js轻量 Canvas 实用类库

 

↑ 测试图

依赖类:

   1 "use strict";
   2 
   3 var __emptyPoint = null, __emptyContext = null, __emptyPointA = null;
   4 
   5 const ColorRefTable = {
   6     "aliceblue": "#f0f8ff",
   7     "antiquewhite": "#faebd7",
   8     "aqua": "#00ffff",
   9     "aquamarine": "#7fffd4",
  10     "azure": "#f0ffff",
  11     "beige": "#f5f5dc",
  12     "bisque": "#ffe4c4",
  13     "black": "#000000",
  14     "blanchedalmond": "#ffebcd",
  15     "blue": "#0000ff",
  16     "blueviolet": "#8a2be2",
  17     "brown": "#a52a2a",
  18     "burlywood": "#deb887",
  19     "cadetblue": "#5f9ea0",
  20     "chartreuse": "#7fff00",
  21     "chocolate": "#d2691e",
  22     "coral": "#ff7f50",
  23     "cornsilk": "#fff8dc",
  24     "crimson": "#dc143c",
  25     "cyan": "#00ffff",
  26     "darkblue": "#00008b",
  27     "darkcyan": "#008b8b",
  28     "darkgoldenrod": "#b8860b",
  29     "darkgray": "#a9a9a9",
  30     "darkgreen": "#006400",
  31     "darkgrey": "#a9a9a9",
  32     "darkkhaki": "#bdb76b",
  33     "darkmagenta": "#8b008b",
  34     "firebrick": "#b22222",
  35     "darkolivegreen": "#556b2f",
  36     "darkorange": "#ff8c00",
  37     "darkorchid": "#9932cc",
  38     "darkred": "#8b0000",
  39     "darksalmon": "#e9967a",
  40     "darkseagreen": "#8fbc8f",
  41     "darkslateblue": "#483d8b",
  42     "darkslategray": "#2f4f4f",
  43     "darkslategrey": "#2f4f4f",
  44     "darkturquoise": "#00ced1",
  45     "darkviolet": "#9400d3",
  46     "deeppink": "#ff1493",
  47     "deepskyblue": "#00bfff",
  48     "dimgray": "#696969",
  49     "dimgrey": "#696969",
  50     "dodgerblue": "#1e90ff",
  51     "floralwhite": "#fffaf0",
  52     "forestgreen": "#228b22",
  53     "fuchsia": "#ff00ff",
  54     "gainsboro": "#dcdcdc",
  55     "ghostwhite": "#f8f8ff",
  56     "gold": "#ffd700",
  57     "goldenrod": "#daa520",
  58     "gray": "#808080",
  59     "green": "#008000",
  60     "greenyellow": "#adff2f",
  61     "grey": "#808080",
  62     "honeydew": "#f0fff0",
  63     "hotpink": "#ff69b4",
  64     "indianred": "#cd5c5c",
  65     "indigo": "#4b0082",
  66     "ivory": "#fffff0",
  67     "khaki": "#f0e68c",
  68     "lavender": "#e6e6fa",
  69     "lavenderblush": "#fff0f5",
  70     "lawngreen": "#7cfc00",
  71     "lemonchiffon": "#fffacd",
  72     "lightblue": "#add8e6",
  73     "lightcoral": "#f08080",
  74     "lightcyan": "#e0ffff",
  75     "lightgoldenrodyellow": "#fafad2",
  76     "lightgray": "#d3d3d3",
  77     "lightgreen": "#90ee90",
  78     "lightgrey": "#d3d3d3",
  79     "lightpink": "#ffb6c1",
  80     "lightsalmon": "#ffa07a",
  81     "lightseagreen": "#20b2aa",
  82     "lightskyblue": "#87cefa",
  83     "lightslategray": "#778899",
  84     "lightslategrey": "#778899",
  85     "lightsteelblue": "#b0c4de",
  86     "lightyellow": "#ffffe0",
  87     "lime": "#00ff00",
  88     "limegreen": "#32cd32",
  89     "linen": "#faf0e6",
  90     "magenta": "#ff00ff",
  91     "maroon": "#800000",
  92     "mediumaquamarine": "#66cdaa",
  93     "mediumblue": "#0000cd",
  94     "mediumorchid": "#ba55d3",
  95     "mediumpurple": "#9370db",
  96     "mediumseagreen": "#3cb371",
  97     "mediumslateblue": "#7b68ee",
  98     "mediumspringgreen": "#00fa9a",
  99     "mediumturquoise": "#48d1cc",
 100     "mediumvioletred": "#c71585",
 101     "midnightblue": "#191970",
 102     "mintcream": "#f5fffa",
 103     "mistyrose": "#ffe4e1",
 104     "moccasin": "#ffe4b5",
 105     "navajowhite": "#ffdead",
 106     "navy": "#000080",
 107     "oldlace": "#fdf5e6",
 108     "olive": "#808000",
 109     "olivedrab": "#6b8e23",
 110     "orange": "#ffa500",
 111     "orangered": "#ff4500",
 112     "orchid": "#da70d6",
 113     "palegoldenrod": "#eee8aa",
 114     "palegreen": "#98fb98",
 115     "paleturquoise": "#afeeee",
 116     "palevioletred": "#db7093",
 117     "papayawhip": "#ffefd5",
 118     "peachpuff": "#ffdab9",
 119     "peru": "#cd853f",
 120     "pink": "#ffc0cb",
 121     "plum": "#dda0dd",
 122     "powderblue": "#b0e0e6",
 123     "purple": "#800080",
 124     "red": "#ff0000",
 125     "rosybrown": "#bc8f8f",
 126     "royalblue": "#4169e1",
 127     "saddlebrown": "#8b4513",
 128     "salmon": "#fa8072",
 129     "sandybrown": "#f4a460",
 130     "seagreen": "#2e8b57",
 131     "seashell": "#fff5ee",
 132     "sienna": "#a0522d",
 133     "silver": "#c0c0c0",
 134     "skyblue": "#87ceeb",
 135     "slateblue": "#6a5acd",
 136     "slategray": "#708090",
 137     "slategrey": "#708090",
 138     "snow": "#fffafa",
 139     "springgreen": "#00ff7f",
 140     "steelblue": "#4682b4",
 141     "tan": "#d2b48c",
 142     "teal": "#008080",
 143     "thistle": "#d8bfd8",
 144     "tomato": "#ff6347",
 145     "turquoise": "#40e0d0",
 146     "violet": "#ee82ee",
 147     "wheat": "#f5deb3",
 148     "white": "#ffffff",
 149     "whitesmoke": "#f5f5f5",
 150     "yellow": "#ffff00",
 151     "yellowgreen": "#9acd32"
 152 },
 153 
 154 UTILS = {
 155 
 156     emptyArray(arr){
 157         return !Array.isArray(arr) || arr.length === 0;
 158     },
 159 
 160     createContext(w = 1, h = 1){
 161         const canvas = document.createElement("canvas"),
 162         context = canvas.getContext("2d");
 163         canvas.width = w;
 164         canvas.height = h;
 165         return context;
 166     },
 167 
 168     isObject(obj){
 169         
 170         return obj !== null && typeof obj === "object" && Array.isArray(obj) === false;
 171         
 172     },
 173     
 174     isNumber(num){
 175 
 176         return typeof num === "number" && isNaN(num) === false;
 177 
 178     },
 179 
 180     //获取最后一个点后面的字符
 181     getFileType(string){
 182         let type = "", str = string.split('').reverse().join('');
 183         for(let k = 0, len = str.length; k < len; k++){
 184             if(str[k] === ".") break;
 185             type += str[k];
 186         }
 187         return type.split('').reverse().join('');
 188     },
 189 
 190     //删除 string 所有的空格
 191     deleteSpaceAll(str){
 192         const len = str.length;
 193         var result = '';
 194         for(let i = 0; i < len; i++){
 195             if(str[i] !== '') result += str[i]
 196         }
 197 
 198         return result
 199     },
 200 
 201     //删除 string 两边空格
 202     removeSpaceSides(string){
 203 
 204         return string.replace(/(^\s*)|(\s*$)/g, "");
 205 
 206     },
 207 
 208     //返回 num 与 num1 之间的随机数
 209     random(num, num1){
 210         
 211         if(num < num1) return Math.random() * (num1 - num) + num;
 212 
 213         else if(num > num1) return Math.random() * (num - num1) + num1;
 214 
 215         else return num;
 216         
 217     },
 218 
 219     //生成 UUID
 220     generateUUID: function (){
 221         const _lut = [];
 222     
 223         for ( let i = 0; i < 256; i ++ ) {
 224     
 225             _lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 );
 226     
 227         }
 228     
 229         return function (){
 230             const d0 = Math.random() * 0xffffffff | 0;
 231             const d1 = Math.random() * 0xffffffff | 0;
 232             const d2 = Math.random() * 0xffffffff | 0;
 233             const d3 = Math.random() * 0xffffffff | 0;
 234             const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' +
 235             _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' +
 236             _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] +
 237             _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ];
 238     
 239             return uuid.toLowerCase(); //toLowerCase() 这里展平连接的字符串以节省堆内存空间
 240         }
 241     }(),
 242 
 243     //欧几里得距离(两点的直线距离)
 244     distance(x, y, x1, y1){
 245         
 246         return Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2));
 247 
 248     },
 249 
 250     downloadFile(blob, fileName){
 251         const link = document.createElement("a");
 252         link.href = URL.createObjectURL(blob);
 253         link.download = fileName;
 254         link.click();
 255     },
 256 
 257     loadFileJSON(callback){
 258         const input = document.createElement("input");
 259         input.type = "file";
 260         input.accept = ".json";
 261         
 262         input.onchange = a => {
 263             if(a.target.files.length === 0) return;
 264             const fr = new FileReader();
 265             fr.onloadend = b => callback(b.target.result);
 266             fr.readAsText(a.target.files[0]); //fr.readAsDataURL(a.target.files[0]);
 267         }
 268         
 269         input.click();
 270     },
 271 
 272     get emptyPoint(){
 273         if(__emptyPoint === null) __emptyPoint = new Point();
 274         return __emptyPoint;
 275     },
 276 
 277     get emptyPointA(){
 278         if(__emptyPointA === null) __emptyPointA = new Point();
 279         return __emptyPointA;
 280     },
 281 
 282     get emptyContext(){
 283         if(__emptyContext === null) __emptyContext = document.createElement("canvas").getContext('2d')
 284         return __emptyContext;
 285     },
 286 
 287 }
 288 
 289 
 290 
 291 
 292 /** Ajax
 293 parameter:
 294     option = {
 295         url:        可选, 默认 ''
 296         method:        可选, post 或 get请求, 默认 post
 297         asy:        可选, 是否异步执行, 默认 true
 298         success:    可选, 成功回调, 默认 null
 299         error:        可选, 超时或失败调用, 默认 null
 300         change:        可选, 请求状态改变时调用, 默认 null
 301         data:        可选, 如果定义则在初始化时自动执行.send(data)方法
 302     }
 303 
 304 demo:
 305     const data = `email=${email}&password=${password}`,
 306 
 307     //默认 post 请求:
 308     ajax = new Ajax({
 309         url: './login',
 310         data: data,
 311         success: mes => console.log(mes),
 312     });
 313     
 314     //get 请求:
 315     ajax.method = "get";
 316     ajax.send(data);
 317 */
 318 class Ajax{
 319     
 320     constructor(option = {}){
 321         this.url = option.url || "";
 322         this.method = option.method || "post";
 323         this.asy = typeof option.asy === "boolean" ? option.asy : true;
 324         this.success = option.success || null;
 325         this.error = option.error || null;
 326         this.change = option.change || null;
 327 
 328         //init XML
 329         this.xhr = new XMLHttpRequest();
 330 
 331         this.xhr.onerror = this.xhr.ontimeout = option.error || null;
 332 
 333         this.xhr.onreadystatechange = event => {
 334         
 335             if(event.target.readyState === 4 && event.target.status === 200){
 336 
 337                 if(this.success !== null) this.success(event.target.responseText, event);
 338                 
 339             }
 340 
 341             else if(this.change !== null) this.change(event);
 342 
 343         }
 344 
 345         if(option.data) this.send(option.data);
 346     }
 347 
 348     send(data = ""){
 349         if(this.method === "get"){
 350             this.xhr.open(this.method, this.url+"?"+data, this.asy);
 351             this.xhr.send();
 352         }
 353         
 354         else if(this.method === "post"){
 355             this.xhr.open(this.method, this.url, this.asy);
 356             this.xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
 357             this.xhr.send(data);
 358         }
 359     }
 360     
 361 }
 362 
 363 
 364 
 365 
 366 /* IndexedDB 本地数据库
 367 
 368 parameter:
 369     name: String;                //需要打开的数据库名称(如果不存在则会新建一个) 必须
 370     done: Function(IndexedDB);    //链接数据库成功时的回调 默认 null
 371     version: Number;             //数据库版本(高版本数据库将覆盖低版本的数据库) 默认 1 
 372 
 373 attribute:
 374     database: IndexedDB;            //链接完成的数据库对象
 375     transaction: IDBTransaction;    //事务管理(读和写)
 376     objectStore: IDBObjectStore;    //当前的事务
 377 
 378 method:
 379     set(data, key, callback)        //添加或更新
 380     get(key, callback)                //获取
 381     delete(key, callback)            //删除
 382 
 383     traverse(callback)                //遍历
 384     getAll(callback)                //获取全部
 385     clear(callback)                    //清理所以数据
 386     close()                         //关闭数据库链接
 387 
 388 readOnly:
 389 
 390 static:
 391     indexedDB: Object;
 392 
 393 demo:
 394     
 395     new IndexedDB('TEST', db => {
 396 
 397         conosle.log(db);
 398 
 399     });
 400 
 401 */
 402 class IndexedDB{
 403 
 404     static indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
 405 
 406     get objectStore(){ //每个事务只能使用一次, 所以每次都需要重新创建
 407         return this.database.transaction(this.name, 'readwrite').objectStore(this.name);
 408     }
 409 
 410     constructor(name, done = null, version = 1){
 411 
 412         if(IndexedDB.indexedDB === undefined) return console.error("IndexedDB: 不支持IndexedDB");
 413         
 414         if(typeof name !== 'string') return console.warn('IndexedDB: 参数错误');
 415 
 416         this.name = name;
 417         this.database = null;
 418 
 419         const request = IndexedDB.indexedDB.open(name, version);
 420         
 421         request.onupgradeneeded = (e)=>{ //数据库不存在 或 版本号不同时 触发
 422             if(!this.database) this.database = e.target.result;
 423             if(this.database.objectStoreNames.contains(name) === false) this.database.createObjectStore(name);
 424         }
 425         
 426         request.onsuccess = (e)=>{
 427             this.database = e.target.result;
 428             if(typeof done === 'function') done(this);
 429         }
 430         
 431         request.onerror = (e)=>{
 432             console.error(e);
 433         }
 434         
 435     }
 436 
 437     close(){
 438 
 439         return this.database.close();
 440 
 441     }
 442 
 443     clear(callback){
 444         
 445         this.objectStore.clear().onsuccess = callback;
 446         
 447     }
 448 
 449     traverse(callback){
 450         
 451         this.objectStore.openCursor().onsuccess = callback;
 452 
 453     }
 454 
 455     set(data, key = 0, callback){
 456         
 457         this.objectStore.put(data, key).onsuccess = callback;
 458 
 459     }
 460     
 461     get(key = 0, callback){
 462 
 463         this.objectStore.get(key).onsuccess = callback;
 464         
 465     }
 466 
 467     del(key = 0, callback){
 468 
 469         this.objectStore.delete(key).onsuccess = callback;
 470 
 471     }
 472     
 473     getAll(callback){
 474 
 475         this.objectStore.getAll().onsuccess = callback;
 476 
 477     }
 478 
 479 }
 480 
 481 
 482 
 483 
 484 /* TreeStruct 树结构基类
 485 
 486 attribute:
 487     parent: TreeStruct;
 488     children: Array[TreeStruct];
 489 
 490 method:
 491     add(v: TreeStruct): v;         //v添加到自己的子集
 492     remove(v: TreeStruct): v;     //删除v, 前提v必须是自己的子集
 493     export(): Array[Object];    //TreeStruct 转为 可导出的结构, 包括其所有的后代
 494 
 495     getPath(v: TreeStruct): Array[TreeStruct];     //获取自己到v的路径
 496 
 497     traverse(callback: Function): undefined;  //迭代自己的每一个后代, 包括自己
 498         callback(value: TreeStruct); //如返回 "continue" 则不在迭代其后代(不是结束迭代, 而是只结束当前节点的后代);
 499 
 500     traverseUp(callback): undefined; //向上遍历每一个父, 包括自己
 501         callback(value: TreeStruct); //如返回 "break" 立即停止遍历;
 502 
 503 static:
 504     import(arr: Array[Object]): TreeStruct; //.export() 返回的 arr 转为 TreeStruct
 505 
 506 */
 507 class TreeStruct{
 508 
 509     static import(arr){
 510 
 511         //json = JSON.parse(json);
 512         const len = arr.length;
 513 
 514         for(let k = 0, v; k < len; k++){
 515             v = Object.assign(new TreeStruct(), arr[k]);
 516             v.parent = arr[arr[k].parent] || null;
 517             if(v.parent !== null) v.parent.add(v);
 518             arr[k] = v;
 519         }
 520 
 521         return arr[0];
 522 
 523     }
 524 
 525     constructor(){
 526         this.parent = null;
 527         this.children = [];
 528     }
 529 
 530     getPath(v){
 531 
 532         var path;
 533 
 534         const pathA = [];
 535         this.traverseUp(tar => {
 536             if(v === tar){
 537                 path = pathA;
 538                 return "break";
 539             }
 540             pathA.push(tar);
 541         });
 542 
 543         if(path) return path;
 544 
 545         const pathB = [];
 546         v.traverseUp(tar => {
 547             if(this === tar){
 548                 path = pathB.reverse();
 549                 return "break";
 550             }
 551             else{
 552                 let i = pathA.indexOf(tar);
 553                 if(i !== -1){
 554                     pathA.splice(i);
 555                     pathA.push(tar);
 556                     path = pathA.concat(pathB.reverse());
 557                     return "break";
 558                 }
 559             }
 560             pathB.push(tar);
 561         });
 562 
 563         return path;
 564         
 565     }
 566 
 567     add(v){
 568         v.parent = this;
 569         if(this.children.includes(v) === false) this.children.push(v);
 570         
 571         return v;
 572     }
 573 
 574     remove(v){
 575         const i = this.children.indexOf(v);
 576         if(i !== -1) this.children.splice(i, 1);
 577         v.parent = null;
 578 
 579         return v;
 580     }
 581 
 582     traverse(callback, key = 0){
 583 
 584         if(callback(this, key) !== "continue"){
 585 
 586             for(let k = 0, len = this.children.length; k < len; k++){
 587 
 588                 this.children[k].traverse(callback, k);
 589     
 590             }
 591 
 592         }
 593 
 594     }
 595 
 596     traverseUp(callback){
 597 
 598         var par = this.parent;
 599 
 600         while(par !== null){
 601             if(callback(par) === "break") return;
 602             par = par.parent;
 603         }
 604 
 605     }
 606 
 607     export(){
 608 
 609         const result = [], arr = [];
 610         var obj = null;
 611 
 612         this.traverse(v => {
 613             obj = Object.assign({}, v);
 614             obj.parent = arr.indexOf(v.parent);
 615             delete obj.children;
 616             result.push(obj);
 617             arr.push(v);
 618         });
 619         
 620         return result; //JSON.stringify(result);
 621 
 622     }
 623 
 624 }
 625 
 626 Object.defineProperties(TreeStruct.prototype, {
 627     
 628     isTreeStruct: {
 629         configurable: false,
 630         enumerable: false,
 631         writable: false,
 632         value: true,
 633     }
 634 
 635 });
 636 
 637 
 638 
 639 
 640 /* Point
 641 parameter: 
 642     x = 0, y = 0;
 643 
 644 attribute
 645     x, y: Number;
 646 
 647 method:
 648     set(x, y): this;
 649     angle(): Number;
 650     copy(point): this;
 651     clone(): Point;
 652     distance(point): Number;            //获取欧几里得距离
 653     distanceMHD(point): Number;            //获取曼哈顿距离
 654     distanceCompare(point): Number;        //获取用于比较的距离(相对于.distance() 效率更高)
 655     equals(point): Bool;                //是否恒等
 656     reverse(): this;                    //取反值
 657     rotate(origin: Object{x,y}, angle): this;    //旋转点
 658     normalize(): this;                    //归一
 659 */
 660 class Point{
 661 
 662     constructor(x = 0, y = 0){
 663         this.x = x;
 664         this.y = y;
 665     }
 666 
 667     set(x = 0, y = 0){
 668         this.x = x;
 669         this.y = y;
 670 
 671         return this;
 672     }
 673 
 674     angle(){
 675 
 676         return Math.atan2(this.y, this.x);
 677 
 678     }
 679 
 680     copy(point){
 681         
 682         this.x = point.x;
 683         this.y = point.y;
 684         return this;
 685         //return Object.assign(this, point);
 686 
 687     }
 688     
 689     clone(){
 690 
 691         return new this.constructor().copy(this);
 692         //return Object.assign(new this.constructor(), this);
 693         
 694     }
 695 
 696     distance(point){
 697         
 698         return Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2));
 699 
 700     }
 701 
 702     distanceMHD(point){
 703 
 704         return Math.abs(this.x - point.x) + Math.abs(this.y - point.y);
 705 
 706     }
 707 
 708     distanceCompare(point){
 709     
 710         return Math.pow(this.x - point.x, 2) + Math.pow(this.y - point.y, 2);
 711 
 712     }
 713 
 714     equals(point){
 715 
 716         return point.x === this.x && point.y === this.y;
 717 
 718     }
 719 
 720     reverse(){
 721         this.x = -this.x;
 722         this.y = -this.y;
 723 
 724         return this;
 725     }
 726 
 727     rotate(origin, angle){
 728         const c = Math.cos(angle), s = Math.sin(angle), 
 729         x = this.x - origin.x, y = this.y - origin.y;
 730 
 731         this.x = x * c - y * s + origin.x;
 732         this.y = x * s + y * c + origin.y;
 733 
 734         return this;
 735     }
 736 
 737     normalize(){
 738         const len = 1 / (Math.sqrt(this.x * this.x + this.y * this.y) || 1);
 739         this.x *= len;
 740         this.y *= len;
 741 
 742         return this;
 743     }
 744 
 745 /*     add(point){
 746         this.x += point.x;
 747         this.y += point.y;
 748         return this;
 749     }
 750 
 751     sub(point){
 752         this.x -= point.x;
 753         this.y -= point.y;
 754         return this;
 755     }
 756 
 757     multiply(point){
 758         this.x *= point.x;
 759         this.y *= point.y;
 760         return this;
 761     }
 762 
 763     divide(point){
 764         this.x /= point.x;
 765         this.y /= point.y;
 766         return this;
 767     } */
 768 
 769 }
 770 
 771 Object.defineProperties(Point.prototype, {
 772 
 773     isPoint: {
 774         configurable: false,
 775         enumerable: false,
 776         writable: false,
 777         value: true,
 778     }
 779 
 780 });
 781 
 782 
 783 
 784 
 785 /* Rotate
 786 parameter:
 787     x, y, a = 0
 788 
 789 attribute:
 790     angle: Number;
 791     origin: Point;
 792 
 793 method:
 794     set(x, y, a): this;
 795 */
 796 class Rotate{
 797 
 798     constructor(x, y, a = 0){
 799         this.angle = a;
 800         this.origin = new Point(x, y);
 801     }
 802 
 803     set(x, y, a){
 804         this.origin.x = x;
 805         this.origin.y = y;
 806         this.angle = a;
 807         return this;
 808     }
 809 
 810     pos(x, y){
 811         this.origin.x = x;
 812         this.origin.y = y;
 813         return this;
 814     }
 815 
 816     copy(rotate){
 817         this.angle = rotate.angle;
 818         this.origin.copy(rotate.origin);
 819         return this;
 820     }
 821     
 822     clone(){
 823         return new this.constructor().copy(this);
 824     }
 825 
 826     toAngle(v){
 827         this.angle = v / 180 * Math.PI;
 828         return this;
 829     }
 830 
 831 }
 832 
 833 
 834 
 835 
 836 /* Line
 837 parameter: x, y, x1, y1: Number;
 838 attribute: x, y, x1, y1: Number;
 839 method:
 840     set(x, y, x1, y1): this;                    
 841     copy(line): this;
 842     clone(): Line;
 843     containsPoint(x, y): Bool;                             //点是否在线上
 844     intersectPoint(line: Line, point: Point): Point;    //如果不相交则返回null, 否则返回交点Point
 845     isIntersect(line): Bool;                             //this与line是否相交
 846 */
 847 class Line{
 848 
 849     constructor(x = 0, y = 0, x1 = 0, y1 = 0){
 850         this.x = x;
 851         this.y = y;
 852         this.x1 = x1;
 853         this.y1 = y1;
 854     }
 855 
 856     set(x = 0, y = 0, x1 = 0, y1 = 0){
 857         this.x = x;
 858         this.y = y;
 859         this.x1 = x1;
 860         this.y1 = y1;
 861         return this;
 862     }
 863 
 864     copy(line){
 865         this.x = line.x;
 866         this.y = line.y;
 867         this.x1 = line.x1;
 868         this.y1 = line.y1;
 869         return this;
 870         //return Object.assign(this, line);
 871     }
 872     
 873     clone(){
 874         return new this.constructor().copy(this);
 875         //return Object.assign(new this.constructor(), this);
 876     }
 877 
 878     containsPoint(x, y){
 879         return (x - this.x) * (x - this.x1) <= 0 && (y - this.y) * (y - this.y1) <= 0;
 880     }
 881 
 882     intersectPoint(line, point){
 883         //解线性方程组, 求线段交点
 884         //如果分母为0则平行或共线, 不相交
 885         var denominator = (this.y1 - this.y) * (line.x1 - line.x) - (this.x - this.x1) * (line.y - line.y1);
 886         if(denominator === 0) return null;
 887 
 888         //线段所在直线的交点坐标 (x , y)
 889         const x = ((this.x1 - this.x) * (line.x1 - line.x) * (line.y - this.y) 
 890         + (this.y1 - this.y) * (line.x1 - line.x) * this.x 
 891         - (line.y1 - line.y) * (this.x1 - this.x) * line.x) / denominator;
 892 
 893         const y = -((this.y1 - this.y) * (line.y1 - line.y) * (line.x - this.x) 
 894         + (this.x1 - this.x) * (line.y1 - line.y) * this.y 
 895         - (line.x1 - line.x) * (this.y1 - this.y) * line.y) / denominator;
 896 
 897         //判断交点是否在两条线段上
 898         if(this.containsPoint(x, y) && line.containsPoint(x, y)){
 899             point.x = x;
 900             point.y = y;
 901             return point;
 902         }
 903 
 904         return null;
 905     }
 906 
 907     isIntersect(line){
 908         //快速排斥:
 909         //两个线段为对角线组成的矩形,如果这两个矩形没有重叠的部分,那么两条线段是不可能出现重叠的
 910 
 911         //这里的确如此,这一步是判定两矩形是否相交
 912         //1.线段ab的低点低于cd的最高点(可能重合)
 913         //2.cd的最左端小于ab的最右端(可能重合)
 914         //3.cd的最低点低于ab的最高点(加上条件1,两线段在竖直方向上重合)
 915         //4.ab的最左端小于cd的最右端(加上条件2,两直线在水平方向上重合)
 916         //综上4个条件,两条线段组成的矩形是重合的
 917         //特别要注意一个矩形含于另一个矩形之内的情况
 918         if(!(Math.min(this.x,this.x1)<=Math.max(line.x,line.x1) && Math.min(line.y,line.y1)<=Math.max(this.y,this.y1) && Math.min(line.x,line.x1)<=Math.max(this.x,this.x1) && Math.min(this.y,this.y1)<=Math.max(line.y,line.y1))) return false;
 919 
 920         //跨立实验:
 921         //如果两条线段相交,那么必须跨立,就是以一条线段为标准,另一条线段的两端点一定在这条线段的两段
 922         //也就是说a b两点在线段cd的两端,c d两点在线段ab的两端
 923         var u=(line.x-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y-this.y),
 924         v = (line.x1-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y1-this.y),
 925         w = (this.x-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y-line.y),
 926         z = (this.x1-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y1-line.y);
 927         
 928         return u*v <= 0.00000001 && w*z <= 0.00000001;
 929     }
 930 
 931 }
 932 
 933 
 934 
 935 
 936 /* Box 矩形
 937 parameter: 
 938     x = 0, y = 0, w = 0, h = 0;
 939 
 940 attribute:
 941     x,y: Number; 位置
 942     w,h: Number; 大小
 943 
 944     只读
 945     mx, my: Number; //
 946 
 947 method:
 948     set(x, y, w, h): this;
 949     pos(x, y): this; //设置位置
 950     size(w, h): this; //设置大小
 951     setFromRotate(rotate: Rotate): this;        //通过 rotate 旋转自己
 952     setFromShapeRect(shapeRect): this;            //ShapeRect 转为 Box (忽略 ShapeRect 的旋转和缩放)
 953     setFromCircle(circle, inner: Bool): this;    //
 954     setFromPolygon(polygon, inner = true): this;//
 955     toArray(array: Array, index: Integer): this;
 956     copy(box): this;                             //复制
 957     clone(): Box;                                  //克隆
 958     center(box): this;                            //设置位置在box居中
 959     distance(x, y): Number;                     //左上角原点 与 x,y 的直线距离
 960     isEmpty(): Boolean;                         //.w.h是否小于等于零
 961     maxX(): Number;                             //返回 max x(this.x+this.w);
 962     maxY(): Number;                             //返回 max y(this.y+this.h);
 963     expand(box): undefined;                     //扩容; 把box合并到this
 964     equals(box): Boolean;                         //this与box是否恒等
 965     intersectsBox(box): Boolean;                 //box与this是否相交(box在this内部也会返回true)
 966     containsPoint(x, y): Boolean;                 //x,y点是否在this内
 967     containsBox(box): Boolean;                    //box是否在this内(只是相交返回fasle)
 968     computeOverflow(b: Box, r: Box): undefined;    //this相对b超出的部分赋值到r; r的size小于或等于0的话说明完全超出
 969 */
 970 class Box{
 971 
 972     get mx(){
 973         return this.x + this.w;
 974     }
 975 
 976     get my(){
 977         return this.y + this.h;
 978     }
 979 
 980     get cx(){
 981         return this.w / 2 + this.x;
 982     }
 983 
 984     get cy(){
 985         return this.h / 2 + this.y;
 986     }
 987 
 988     constructor(x = 0, y = 0, w = 0, h = 0){
 989         //this.set(x, y, w, h);
 990         this.x = x;
 991         this.y = y;
 992         this.w = w;
 993         this.h = h;
 994     }
 995     
 996     set(x, y, w, h){
 997         this.x = x;
 998         this.y = y;
 999         this.w = w;
1000         this.h = h;
1001         return this;
1002     }
1003 
1004     pos(x, y){
1005         this.x = x;
1006         this.y = y;
1007         return this;
1008     }
1009     
1010     size(w, h){
1011         this.w = w;
1012         this.h = h;
1013         return this;
1014     }
1015 
1016     setFromRotate(rotate){
1017         var minX = this.x, minY = this.y, maxX = 0, maxY = 0;
1018         const point = UTILS.emptyPoint,
1019         run = function (){
1020             if(point.x < minX) minX = point.x;
1021             else if(point.x > maxX) maxX = point.x;
1022             if(point.y < minY) minY = point.y;
1023             else if(point.y > maxY) maxY = point.y;
1024         }
1025 
1026         point.set(this.x, this.y).rotate(rotate.origin, rotate.angle); run();
1027         point.set(this.mx, this.y).rotate(rotate.origin, rotate.angle); run();
1028         point.set(this.mx, this.my).rotate(rotate.origin, rotate.angle); run();
1029         point.set(this.x, this.my).rotate(rotate.origin, rotate.angle); run();
1030 
1031         this.x = minX;
1032         this.y = minY;
1033         this.w = maxX - minX;
1034         this.h = maxY - minY;
1035 
1036         return this;
1037     }
1038 
1039     setFromShapeRect(shapeRect){
1040         this.width = shapeRect.width;
1041         this.height = shapeRect.height;
1042         this.x = shapeRect.position.x - this.width / 2;
1043         this.y = shapeRect.position.y - this.height / 2;
1044         return this;
1045     }
1046 
1047     setFromCircle(circle, inner = true){
1048         if(inner === true){
1049             this.x = Math.sin(-135 / 180 * Math.PI) * circle.r + circle.x;
1050             this.y = Math.cos(-135 / 180 * Math.PI) * circle.r + circle.y;
1051             this.w = this.h = Math.sin(135 / 180 * Math.PI) * circle.r + circle.x - this.x;
1052         }
1053 
1054         else{
1055             this.x = circle.x - circle.r;
1056             this.y = circle.y - circle.r;
1057             this.w = this.h = circle.r * 2;
1058         }
1059         return this;
1060     }
1061 
1062     setFromPolygon(polygon, inner = true){
1063         if(inner === true){
1064             console.warn('Box: 暂不支持第二个参数为true');
1065         }
1066 
1067         else{
1068             const len = polygon.path.length;
1069             let x = Infinity, y = Infinity, mx = 0, my = 0;
1070             for(let k = 0, v; k < len; k+=2){
1071                 v = polygon.path[k];
1072                 if(v < x) x = v;
1073                 else if(v > mx) mx = v;
1074 
1075                 v = polygon.path[k+1];
1076                 if(v < y) y = v;
1077                 else if(v > my) my = v;
1078 
1079             }
1080 
1081             this.set(x, y, mx - x, my - y);
1082 
1083         }
1084         return this;
1085     }
1086 
1087     toArray(array, index){
1088         array[index] = this.x;
1089         array[index+1] = this.y;
1090         array[index+2] = this.w;
1091         array[index+3] = this.h;
1092 
1093         return this;
1094     }
1095 
1096     copy(box){
1097         this.x = box.x;
1098         this.y = box.y;
1099         this.w = box.w;
1100         this.h = box.h;
1101         return this;
1102         //return Object.assign(this, box); //this.set(box.x, box.y, box.w, box.h);
1103     }
1104     
1105     clone(){
1106         return new this.constructor().copy(this);
1107         //return Object.assign(new this.constructor(), this);
1108     }
1109 
1110     center(box){
1111         this.x = (box.w - this.w) / 2 + box.x;
1112         this.y = (box.h - this.h) / 2 + box.y;
1113         return this;
1114     }
1115 
1116     distance(x, y){
1117         return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
1118     }
1119 
1120     isEmpty(){
1121         return this.w <= 0 || this.h <= 0;
1122     }
1123 
1124     maxX(){
1125         return this.x + this.w;
1126     }
1127 
1128     maxY(){
1129         return this.y + this.h;
1130     }
1131 
1132     equals(box){
1133         return this.x === box.x && this.w === box.w && this.y === box.y && this.h === box.h;
1134     }
1135 
1136     expand(box){
1137         var v = Math.min(this.x, box.x);
1138         this.w = Math.max(this.x + this.w - v, box.x + box.w - v);
1139         this.x = v;
1140 
1141         v = Math.min(this.y, box.y);
1142         this.h = Math.max(this.y + this.h - v, box.y + box.h - v);
1143         this.y = v;
1144     }
1145 
1146     intersectsBox(box){
1147         return box.x + box.w < this.x || box.x > this.x + this.w || box.y + box.h < this.y || box.y > this.y + this.h ? false : true;
1148     }
1149 
1150     containsPoint(x, y){
1151         return x < this.x || x > this.x + this.w || y < this.y || y > this.y + this.h ? false : true;
1152     }
1153 
1154     containsBox(box){
1155         return this.x <= box.x && box.x + box.w <= this.x + this.w && this.y <= box.y && box.y + box.h <= this.y + this.h;
1156     }
1157 
1158     computeOverflow(p, r){
1159         r["copy"](this);
1160         
1161         if(this["x"] < p["x"]){
1162             r["x"] = p["x"];
1163             r["w"] -= p["x"] - this["x"];
1164         }
1165 
1166         if(this["y"] < p["y"]){
1167             r["y"] = p["y"];
1168             r["h"] -= p["y"] - this["y"];
1169         }
1170 
1171         var m = p["x"] + p["w"];
1172         if(r["x"] + r["w"] > m) r["w"] = m - r["x"];
1173 
1174         m = p["y"] + p["h"];
1175         if(r["y"] + r["h"] > m) r["h"] = m - r["y"];
1176     }
1177     
1178 }
1179 
1180 Object.defineProperties(Box.prototype, {
1181 
1182     isBox: {
1183         configurable: false,
1184         enumerable: false,
1185         writable: false,
1186         value: true,
1187     },
1188 
1189 });
1190 
1191 
1192 
1193 
1194 /* Circle 圆形
1195 parameter:
1196 attribute:
1197     x,y: Number; 中心点
1198     r: Number; 半径
1199 
1200     //只读
1201     r2: Number; //返回直径 r*2
1202 
1203 method:
1204     set(x, y, r): this;
1205     pos(x, y): this;
1206     copy(circle: Circle): this;
1207     clone(): Circle;
1208     distance(x, y): Number;
1209     equals(circle: Circle): Bool;
1210     containsPoint(x, y): Bool; 
1211     intersectsCircle(circle: Circle): Bool;
1212     intersectsBox(box: Box): Bool;
1213     setFromBox(box, inner = true): this;
1214 
1215 */
1216 class Circle{
1217 
1218     get r2(){
1219         return this.r * 2;
1220     }
1221 
1222     constructor(x = 0, y = 0, r = -1){
1223         //this.set(0, 0, -1);
1224         this.x = x;
1225         this.y = y;
1226         this.r = r;
1227     }
1228 
1229     set(x, y, r){
1230         this.x = x;
1231         this.y = y;
1232         this.r = r;
1233 
1234         return this;
1235     }
1236 
1237     setFromBox(box, world = true, inner = true){
1238         this.x = box.w / 2 + (world === true ? box.x : 0);
1239         this.y = box.h / 2 + (world === true ? box.y : 0);
1240         this.r = inner === true ? Math.min(box.w, box.h) / 2 : UTILS.distance(0, 0, box.w, box.h) / 2;
1241     
1242         return this;
1243     }
1244 
1245     toArray(array, index){
1246         array[index] = this.x;
1247         array[index+1] = this.y;
1248         array[index+2] = this.r;
1249         
1250         return this;
1251     }
1252 
1253     pos(x, y){
1254         this.x = x;
1255         this.y = y;
1256 
1257         return this;
1258     }
1259 
1260     copy(circle){
1261         this.r = circle.r;
1262         this.x = circle.x;
1263         this.y = circle.y;
1264 
1265         return this;
1266     }
1267 
1268     clone(){
1269 
1270         return new this.constructor().copy(this);
1271 
1272     }
1273 
1274     distance(x, y){
1275         
1276         return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
1277 
1278     }
1279 
1280     equals(circle){
1281 
1282         return circle.x === this.x && circle.y === this.y && circle.r === this.r;
1283 
1284     }
1285 
1286     containsPoint(x, y){
1287 
1288         return (Math.pow(x - this.x, 2) + Math.pow(y - this.y, 2) <= Math.pow(this.r, 2));
1289 
1290     }
1291 
1292     intersectsCircle(circle){
1293 
1294         return (Math.pow(circle.x - this.x, 2) + Math.pow(circle.y - this.y, 2) <= Math.pow(circle.r + this.r, 2));
1295 
1296     }
1297 
1298     intersectsBox(box){
1299         
1300         return (Math.pow(Math.max(box.x, Math.min(box.x + box.w, this.x)) - this.x, 2) + Math.pow(Math.max(box.y, Math.min(box.y + box.h, this.y)) - this.y, 2) <= Math.pow(this.r, 2));
1301     
1302     }
1303 
1304 }
1305 
1306 Object.defineProperties(Circle.prototype, {
1307 
1308     isCircle: {
1309         configurable: false,
1310         enumerable: false,
1311         writable: false,
1312         value: true,
1313     }
1314 
1315 });
1316 
1317 
1318 
1319 
1320 /* Polygon 多边形
1321 
1322 parameter: 
1323     path: Array[x, y];
1324 
1325 attribute:
1326 
1327     //只读属性
1328     path: Array[x, y]; 
1329 
1330 method:
1331     add(x, y): this;             //x,y添加至path;
1332     containsPoint(x, y): Bool;    //x,y是否在多边形的内部(注意: 在路径上也返回 true)
1333     
1334 */
1335 class Polygon{
1336 
1337     #position = null;
1338     #path2D = null;
1339 
1340     get path(){
1341         
1342         return this.#position;
1343 
1344     }
1345 
1346     constructor(path = []){
1347         this.#position = path;
1348 
1349         this.#path2D = new Path2D();
1350         
1351         var len = path.length;
1352         if(len >= 2){
1353             if(len % 2 !== 0){
1354                 len -= 1;
1355                 path.splice(len, 1);
1356             }
1357 
1358             const con = this.#path2D;
1359             con.moveTo(path[0], path[1]);
1360             for(let k = 2; k < len; k+=2) con.lineTo(path[k], path[k+1]);
1361             
1362         }
1363 
1364     }
1365 
1366     add(x, y){
1367         this.#position.push(x, y);
1368         this.#path2D.lineTo(x, y);
1369         return this;
1370     }
1371 
1372     containsPoint(x, y){
1373         
1374         return UTILS.emptyContext.isPointInPath(this.#path2D, x, y);
1375 
1376     }
1377 
1378     isInPolygon(checkPoint, polygonPoints) {
1379         var counter = 0;
1380         var i;
1381         var xinters;
1382         var p1, p2;
1383         var pointCount = polygonPoints.length;
1384         p1 = polygonPoints[0];
1385         for (i = 1; i <= pointCount; i++) {
1386             p2 = polygonPoints[i % pointCount];
1387             if (
1388                 checkPoint[0] > Math.min(p1[0], p2[0]) &&
1389                 checkPoint[0] <= Math.max(p1[0], p2[0])
1390             ) {
1391                 if (checkPoint[1] <= Math.max(p1[1], p2[1])) {
1392                     if (p1[0] != p2[0]) {
1393                         xinters =
1394                             (checkPoint[0] - p1[0]) *
1395                                 (p2[1] - p1[1]) /
1396                                 (p2[0] - p1[0]) +
1397                             p1[1];
1398                         if (p1[1] == p2[1] || checkPoint[1] <= xinters) {
1399                             counter++;
1400                         }
1401                     }
1402                 }
1403             }
1404             p1 = p2;
1405         }
1406         if (counter % 2 == 0) {
1407             return false;
1408         } else {
1409             return true;
1410         }
1411     }
1412 
1413     containsPolygon(polygon){
1414         const path = polygon.path, len = path.length;
1415         for(let k = 0; k < len; k += 2){
1416             if(this.containsPoint(path[k], path[k+1]) === false) return false;
1417         }
1418 
1419         return true;
1420     }
1421 
1422     toPoints(){
1423         const path = this.path, len = path.length, result = [];
1424         
1425         for(let k = 0; k < len; k += 2) result.push(new Point(path[k], path[k+1]));
1426 
1427         return result;
1428     }
1429 
1430     toLines(){
1431         const path = this.path, len = path.length, result = [];
1432         
1433         for(let k = 0, x = NaN, y; k < len; k += 2){
1434 
1435             if(isNaN(x)){
1436                 x = path[k];
1437                 y = path[k+1];
1438                 continue;
1439             }
1440 
1441             const line = new Line(x, y, path[k], path[k+1]);
1442             
1443             x = line.x1;
1444             y = line.y1;
1445 
1446             result.push(line);
1447 
1448         }
1449 
1450         return result;
1451     }
1452 
1453     merge(polygon){
1454 
1455         const linesA = this.toLines(), linesB = polygon.toLines(), nodes = [], newLines = [],
1456         
1457         pointA = new Point(), pointB = new Point(),
1458 
1459         forEachNodes = (pathA, pathB, funcA = null, funcB = null) => {
1460             for(let k = 0, lenA = pathA.length, lenB = pathB.length; k < lenA; k++){
1461                 if(funcA !== null) funcA(pathA[k]);
1462 
1463                 for(let i = 0; i < lenB; i++){
1464                     if(funcB !== null) funcB(pathB[i], pathA[k]);
1465                 }
1466     
1467             }
1468         }
1469 
1470         if(this.containsPolygon(polygon)){console.log('this -> polygon');
1471             forEachNodes(linesA, linesB, lineA => newLines.push(lineA), (lineB, lineA) => {
1472                 if(lineA.intersectPoint(lineB, pointA) === pointA) newLines.push(pointA.clone());
1473             });
1474 
1475             return newLines;
1476         }
1477 
1478         //收集所有的交点 (保存至 line)
1479         forEachNodes(linesA, linesB, lineA => lineA.nodes = [], (lineB, lineA) => {
1480             if(lineB.nodes === undefined) lineB.nodes = [];
1481             if(lineA.intersectPoint(lineB, pointA) === pointA){
1482                 const node = {
1483                     lineA: lineA, 
1484                     lineB: lineB, 
1485                     point: pointA.clone(),
1486                     disA: pointA.distanceCompare(pointB.set(lineA.x, lineA.y)),
1487                     disB: pointA.distanceCompare(pointB.set(lineB.x, lineB.y)),
1488                 }
1489                 lineA.nodes.push(node);
1490                 lineB.nodes.push(node);
1491                 nodes.push(node);
1492             }
1493         });
1494 
1495         //交点以原点为目标排序
1496         for(let k = 0, sotr = function (a,b){return a.disA - b.disA;}, countA = linesA.length; k < countA; k++) linesA[k].nodes.sort(sotr);
1497         for(let k = 0, sotr = function (a,b){return a.disB - b.disB;}, countB = linesB.length; k < countB; k++) linesB[k].nodes.sort(sotr);
1498 
1499         var _loopTypeA, _loopTypeB;
1500         const result_loop = {
1501             lines: null,
1502             loopType: '',
1503             line: null,
1504             count: 0,
1505             indexed: 0,
1506         },
1507         
1508         //遍历某条线
1509         loop = (lines, index, loopType) => {
1510             const length = lines.length, indexed = lines.length, model = lines === linesA ? polygon : this;
1511         
1512             var line, i = 1;
1513             while(true){
1514                 if(loopType === 'next') index = index === length - 1 ? 0 : index + 1;
1515                 else if(loopType === 'back') index = index === 0 ? length - 1 : index - 1;
1516                 line = lines[index];
1517 
1518                 result_loop.count = line.nodes.length;
1519                 if(result_loop.count !== 0){
1520                     result_loop.lines = lines;
1521                     result_loop.loopType = loopType;
1522                     result_loop.line = line;
1523                     result_loop.indexed = index;
1524                     if(loopType === 'next') addLine(line, model);
1525 
1526                     return result_loop;
1527                 }
1528                 
1529                 addLine(line, model);
1530                 if(indexed === i++) break;
1531 
1532             }
1533             
1534         },
1535 
1536         //更新或创建交点的索引
1537         setNodeIndex = (lines, index, loopType) => {
1538             const line = lines[index], count = line.nodes.length;
1539             if(loopType === undefined) loopType = lines === linesA ? _loopTypeA : _loopTypeB;
1540 
1541             if(loopType === undefined) return;
1542             
1543             if(line.nodeIndex === undefined){
1544                 line.nodeIndex = loopType === 'next' ? 0 : count - 1;
1545                 line.nodeState = count === 1 ? 'end' : 'start';
1546             
1547             }
1548 
1549             else{
1550                 if(line.nodeState === 'end' || line.nodeState === ''){
1551                     line.nodeState = '';
1552                     return;
1553                 }
1554 
1555                 if(loopType === 'next'){
1556                     line.nodeIndex += 1;
1557 
1558                     if(line.nodeIndex === count - 1) line.nodeState = 'end';
1559                     else line.nodeState = 'run';
1560                 }
1561 
1562                 else if(loopType === 'back'){
1563                     line.nodeIndex -= 1;
1564 
1565                     if(line.nodeIndex === 0) line.nodeState = 'end';
1566                     else line.nodeState = 'run';
1567                 }
1568 
1569             }
1570 
1571         },
1572 
1573         //只有在跳线的时候才执行此方法, 如果跳线的话: 某条线上的交点必然在两端;
1574         getLoopType = (lines, index, nodePoint) => {
1575             const line = lines[index], lineNext = lines[index === lines.length - 1 ? 0 : index + 1],
1576 
1577             model = lines === linesA ? polygon : this,
1578             isLineBack = newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false,
1579             isLineNext = newLines.includes(lineNext) === false && model.containsPoint(lineNext.x, lineNext.y) === false;
1580             
1581             if(isLineBack && isLineNext){
1582                 const len = line.nodes.length;
1583                 if(len >= 2){
1584                     if(line.nodes[len - 1].point.equals(nodePoint)) return 'next';
1585                     else if(line.nodes[0].point.equals(nodePoint)) return 'back';
1586                 }
1587                 
1588                 else console.warn('路径复杂', line);
1589                 
1590             }
1591 
1592             else if(isLineNext){
1593                 return 'next';
1594             }
1595 
1596             else if(isLineBack){
1597                 return 'back';
1598             }
1599 
1600             return '';
1601         },
1602 
1603         //添加线至新的形状数组
1604         addLine = (line, model) => {
1605             //if(newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false) newLines.push(line);
1606             if(line.nodes.length === 0) newLines.push(line);
1607             else if(model.containsPoint(line.x, line.y) === false) newLines.push(line);
1608             
1609         },
1610 
1611         //处理拥有交点的线
1612         computeNodes = v => {
1613             if(v === undefined || v.count === 0) return;
1614             
1615             setNodeIndex(v.lines, v.indexed, v.loopType);
1616         
1617             //添加交点
1618             const node = v.line.nodes[v.line.nodeIndex];
1619             if(newLines.includes(node.point) === false) newLines.push(node.point);
1620             else return;
1621 
1622             var lines = v.lines === linesA ? linesB : linesA, 
1623             line = lines === linesA ? node.lineA : node.lineB, 
1624             index = lines.indexOf(line);
1625 
1626             setNodeIndex(lines, index);
1627         
1628             //选择交点状态
1629             var nodeState = line.nodeState !== undefined ? line.nodeState : v.line.nodeState;
1630             if((v.count <= 2 && line.nodes.length > 2) || (v.count > 2 && line.nodes.length <= 2)){
1631                 if(line.nodeState === 'start'){
1632                     const backLine = v.loopType === 'next' ? v.lines[v.indexed === 0 ? v.lines.length-1 : v.indexed-1] : v.lines[v.indexed === v.lines.length-1 ? 0 : v.indexed+1];
1633                     
1634                     if(newLines.includes(backLine) && backLine.nodes.length === 0){
1635                         nodeState = 'run';
1636                     }
1637 
1638                 }
1639                 else if(line.nodeState === 'end'){
1640                     const nextLine = v.loopType === 'next' ? v.lines[v.indexed === v.lines.length-1 ? 0 : v.indexed+1] : v.lines[v.indexed === 0 ? v.lines.length-1 : v.indexed-1];
1641                     const model = v.lines === linesA ? polygon : this;
1642                     if(model.containsPoint(nextLine.x, nextLine.y) === false && nextLine.nodes.length === 0){
1643                         nodeState = 'run';
1644                     }
1645                     
1646                 }
1647             }
1648 
1649             switch(nodeState){
1650 
1651                 //不跳线
1652                 case 'run': 
1653                     if(v.loopType === 'back') addLine(v.line, v.lines === linesA ? polygon : this);
1654                     return computeNodes(loop(v.lines, v.indexed, v.loopType));
1655 
1656                 //跳线
1657                 case 'start': 
1658                 case 'end': 
1659                     const loopType = getLoopType(lines, index, node.point);
1660                     if(loopType !== ''){
1661                         if(lines === linesA) _loopTypeA = loopType;
1662                         else _loopTypeB = loopType;
1663                         if(loopType === 'back') addLine(line, v.lines === linesA ? this : polygon);
1664                         return computeNodes(loop(lines, index, loopType));
1665                     }
1666                     break;
1667 
1668             }
1669 
1670         }
1671         
1672         //获取介入点
1673         var startLine = null;
1674         for(let k = 0, len = nodes.length, node; k < len; k++){
1675             node = nodes[k];
1676             if(node.lineA.nodes.length !== 0){
1677                 startLine = node.lineA;
1678                 if(node.lineA.nodes.length === 1 && node.lineB.nodes.length > 1){
1679                     startLine = node.lineB.nodes[0].lineB;
1680                     result_loop.lines = linesB;
1681                     result_loop.loopType = _loopTypeB = 'next';
1682                 }
1683                 else{
1684                     result_loop.lines = linesA;
1685                     result_loop.loopType = _loopTypeA = 'next';
1686                 }
1687                 result_loop.line = startLine;
1688                 result_loop.count = startLine.nodes.length;
1689                 result_loop.indexed = result_loop.lines.indexOf(startLine);
1690                 break;
1691             }
1692         }
1693 
1694         if(startLine === null){
1695             console.warn('Polygon: 找不到介入点, 终止了合并');
1696             return newLines;
1697         }
1698 
1699         computeNodes(result_loop);
1700     
1701         return newLines;
1702     }
1703 
1704 }
1705 
1706 
1707 
1708 
1709 /* ShapeRect (一般2d矩形的原点在左上, 此矩形类的原点在中间)
1710 parameter: 
1711     width = 0, height = 0
1712 
1713 attribute: 
1714     width, height: Number;     //矩形的宽高
1715     position: Point;        //位置(是旋转和缩放的中心点)
1716     rotation: Number;        //旋转(绕 Z 轴旋转的弧度)
1717     scale: Point;            //缩放
1718 
1719 method:
1720     setFromBox(box): this;                //Box 转为 ShapeRect
1721     compute(): undefined;                //计算出矩形
1722     applyCanvas(context): undefined;    //矩形应用到画布的上下文中
1723 
1724 demo:
1725     const canvasRect = new ShapeRect(100, 150); console.log(canvasRect);
1726 
1727     const canvas = document.createElement("canvas");
1728     canvas.width = WORLD.width;
1729     canvas.height = WORLD.height;
1730     canvas.style = `
1731         position: absolute;
1732         z-index: 9999;
1733         background: rgb(127,127,127);
1734     `;
1735     document.body.appendChild(canvas);
1736 
1737     canvasRect.position.set(300, 300);
1738     canvasRect.rotation = UTILS.toAngle(45);
1739     canvasRect.scale.set(1.5, 1.5);
1740     canvasRect.compute();
1741 
1742     const context = canvas.getContext("2d");
1743     canvasRect.applyCanvas(context);
1744 
1745     context.strokeStyle = "red";
1746     context.stroke();
1747 */
1748 class ShapeRect{
1749 
1750     #dots = [];
1751     #size = new Point();
1752     #min = new Point();
1753     #max = new Point();
1754 
1755     constructor(width = 0, height = 0){
1756         this.width = width;
1757         this.height = height;
1758         this.position = new Point();
1759         this.rotation = 0;
1760         this.scale = new Point(1, 1);
1761     }
1762 
1763     setFromBox(box){
1764         this.width = box.w;
1765         this.height = box.h;
1766         this.position.set(box.cx, box.cy);
1767         return this;
1768     }
1769 
1770     compute(){
1771         //scale
1772         this.#size.set(this.width * this.scale.x, this.height * this.scale.y);
1773         
1774         //position
1775         this.#min.set(this.position.x - this.#size.x / 2, this.position.y - this.#size.y / 2);
1776         this.#max.set(this.#min.x + this.#size.x, this.#min.y + this.#size.y);
1777         
1778         //rotation
1779         const point = UTILS.emptyPoint;
1780         this.#dots.length = 0;
1781         
1782         point.set(this.#min.x, this.#min.y).rotate(this.position, this.rotation);
1783         this.#dots.push(point.x, point.y);
1784 
1785         point.set(this.#max.x, this.#min.y).rotate(this.position, this.rotation);
1786         this.#dots.push(point.x, point.y);
1787 
1788         point.set(this.#max.x, this.#max.y).rotate(this.position, this.rotation);
1789         this.#dots.push(point.x, point.y);
1790 
1791         point.set(this.#min.x, this.#max.y).rotate(this.position, this.rotation);
1792         this.#dots.push(point.x, point.y);
1793 
1794         //缩放旋转后的 min 与 max
1795         //this.#min.set(Math.min(this.#dots[0], this.#dots[2], this.#dots[4], this.#dots[6]), Math.min(this.#dots[1], this.#dots[3], this.#dots[5], this.#dots[7]));
1796         //this.#max.set(Math.max(this.#dots[0], this.#dots[2], this.#dots[4], this.#dots[6]), Math.max(this.#dots[1], this.#dots[3], this.#dots[5], this.#dots[7]))
1797     }
1798 
1799     drawImage(img, context){
1800         UTILS.emptyContext.canvas.width = this.#size.x;
1801         UTILS.emptyContext.canvas.height = this.#size.y;
1802         UTILS.emptyContext.drawImage(img, 0, 0, this.#size.x, this.#size.y);
1803 
1804         const imageData = UTILS.emptyContext.getImageData(0, 0, this.#size.x, this.#size.y),
1805         width = imageData.width, height = imageData.height,
1806         len = width * height, point = UTILS.emptyPoint, center = UTILS.emptyPointA.set(width/2, height/2),
1807 
1808         newImageData = UTILS.emptyContext.createImageData(width, height);
1809         
1810         for(let k = 0; k < len; k++){
1811             point.set(k % width, Math.floor(k / width)).rotate(center, this.rotation);
1812             if(point.x < 0 || point.y < 0 || point.x > newImageData.width-1 || point.y > newImageData.height-1) continue;
1813 
1814             point.set((width * Math.round(point.y) + Math.round(point.x)) * 4, k * 4);
1815             newImageData.data[point.x] = imageData.data[point.y];
1816             newImageData.data[point.x + 1] = imageData.data[point.y + 1];
1817             newImageData.data[point.x + 2] = imageData.data[point.y + 2];
1818             newImageData.data[point.x + 3] = imageData.data[point.y + 3];
1819         }
1820 
1821         context.putImageData(newImageData, 20, 20);
1822     }
1823 
1824     applyCanvas(context){
1825         context.beginPath();
1826         context.moveTo(this.#dots[0], this.#dots[1]);
1827 
1828         for(let k = 2, len = this.#dots.length; k < len; k += 2){
1829             context.lineTo(this.#dots[k], this.#dots[k + 1]);
1830         }
1831 
1832         context.closePath();
1833     }
1834 
1835 }
1836 
1837 
1838 
1839 
1840 /* RGBColor
1841 parameter: 
1842     r, g, b
1843 
1844 method:
1845     set(r, g, b: Number): this;            //rgb: 0 - 255; 第一个参数可以为 css color
1846     setFormHex(hex: Number): this;         //
1847     setFormHSV(h, s, v: Number): this;    //h:0-360; s,v:0-100; 颜色, 明度, 暗度
1848     setFormString(str: String): Number;    //str: 英文|css color; 返回的是透明度 (如果为 rgba 则返回a; 否则总是返回1)
1849 
1850     copy(v: RGBColor): this;
1851     clone(): RGBColor;
1852 
1853     getHex(): Number;
1854     getHexString(): String;
1855     getHSV(result: Object{h, s, v}): result;    //result: 默认是一个新的Object
1856     getRGBA(alpha: Number): String;             //alpha: 0 - 1; 默认 1
1857     getStyle()                                     //.getRGBA()别名
1858 
1859     stringToColor(str: String): String; //str 转为 css color; 如果str不是color格式则返回 ""
1860 
1861 */
1862 class RGBColor{
1863 
1864     constructor(r = 255, g = 255, b = 255){
1865         this.r = r;
1866         this.g = g;
1867         this.b = b;
1868 
1869     }
1870 
1871     copy(v){
1872         this.r = v.r;
1873         this.g = v.g;
1874         this.b = v.b;
1875         return this;
1876     }
1877 
1878     clone(){
1879         return new this.constructor().copy(this);
1880     }
1881 
1882     set(r, g, b){
1883         if(typeof r !== "string"){
1884             this.r = r || 255;
1885             this.g = g || 255;
1886             this.b = b || 255;
1887         }
1888 
1889         else this.setFormString(r);
1890         
1891         return this;
1892     }
1893 
1894     setFormHex(hex){
1895         hex = Math.floor( hex );
1896 
1897         this.r = hex >> 16 & 255;
1898         this.g = hex >> 8 & 255;
1899         this.b = hex & 255;
1900         return this;
1901     }
1902 
1903     setFormHSV(h, s, v){
1904         h = h >= 360 ? 0 : h;
1905         var s=s/100;
1906         var v=v/100;
1907         var h1=Math.floor(h/60) % 6;
1908         var f=h/60-h1;
1909         var p=v*(1-s);
1910         var q=v*(1-f*s);
1911         var t=v*(1-(1-f)*s);
1912         var r,g,b;
1913         switch(h1){
1914             case 0:
1915                 r=v;
1916                 g=t;
1917                 b=p;
1918                 break;
1919             case 1:
1920                 r=q;
1921                 g=v;
1922                 b=p;
1923                 break;
1924             case 2:
1925                 r=p;
1926                 g=v;
1927                 b=t;
1928                 break;
1929             case 3:
1930                 r=p;
1931                 g=q;
1932                 b=v;
1933                 break;
1934             case 4:
1935                 r=t;
1936                 g=p;
1937                 b=v;
1938                 break;
1939             case 5:
1940                 r=v;
1941                 g=p;
1942                 b=q;
1943                 break;
1944         }
1945 
1946         this.r = Math.round(r*255);
1947         this.g = Math.round(g*255);
1948         this.b = Math.round(b*255);
1949         return this;
1950     }
1951 
1952     setFormString(color){
1953         if(typeof color !== "string") return 1;
1954         var _color = this.stringToColor(color);
1955         
1956         if(_color[0] === "#"){
1957             const len = _color.length;
1958             if(len === 4){
1959                 _color = _color.slice(1);
1960                 this.setFormHex(parseInt("0x"+_color + "" + _color));
1961             }
1962             else if(len === 7) this.setFormHex(parseInt("0x"+_color.slice(1)));
1963             
1964         }
1965 
1966         else if(_color[0] === "r" && _color[1] === "g" && _color[2] === "b"){
1967             const arr = [];
1968             for(let k = 0, len = _color.length, v = "", is = false; k < len; k++){
1969                 
1970                 if(is === true){
1971                     if(_color[k] === "," || _color[k] === ")"){
1972                         arr.push(parseFloat(v));
1973                         v = "";
1974                     }
1975                     else v += _color[k];
1976                     
1977                 }
1978 
1979                 else if(_color[k] === "(") is = true;
1980                 
1981             }
1982 
1983             this.set(arr[0], arr[1], arr[2]);
1984             return arr[3] === undefined ? 1 : arr[3];
1985         }
1986         
1987         return 1;
1988     }
1989 
1990     getHex(){
1991 
1992         return Math.max( 0, Math.min( 255, this.r ) ) << 16 ^ Math.max( 0, Math.min( 255, this.g ) ) << 8 ^ Math.max( 0, Math.min( 255, this.b ) ) << 0;
1993 
1994     }
1995 
1996     getHexString(){
1997 
1998         return '#' + ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );
1999 
2000     }
2001 
2002     getHSV(result){
2003         result = result || {}
2004         var r=this.r/255;
2005         var g=this.g/255;
2006         var b=this.b/255;
2007         var h,s,v;
2008         var min=Math.min(r,g,b);
2009         var max=v=Math.max(r,g,b);
2010         var l=(min+max)/2;
2011         var difference = max-min;
2012         
2013         if(max==min){
2014             h=0;
2015         }else{
2016             switch(max){
2017                 case r: h=(g-b)/difference+(g < b ? 6 : 0);break;
2018                 case g: h=2.0+(b-r)/difference;break;
2019                 case b: h=4.0+(r-g)/difference;break;
2020             }
2021             h=Math.round(h*60);
2022         }
2023         if(max==0){
2024             s=0;
2025         }else{
2026             s=1-min/max;
2027         }
2028         s=Math.round(s*100);
2029         v=Math.round(v*100);
2030         result.h = h;
2031         result.s = s;
2032         result.v = v;
2033         return result;
2034     }
2035 
2036     getStyle(){
2037         return this.getRGBA(1);
2038     }
2039 
2040     getRGBA(alpha){
2041         alpha = typeof alpha === 'number' ? alpha : 1;
2042         return 'rgba('+this.r+','+this.g+','+this.b+','+alpha+')';
2043     }
2044 
2045     stringToColor(str){
2046         var _color = "";
2047         for(let k = 0, len = str.length; k < len; k++){
2048             if(str[k] === " ") continue;
2049             _color += str[k];
2050         }
2051         
2052         if(_color[0] === "#" || (_color[0] === "r" && _color[1] === "g" && _color[2] === "b")) return _color;
2053 
2054         return ColorRefTable[_color] || "";
2055     }
2056 
2057 }
2058 
2059 Object.defineProperties(RGBColor.prototype, {
2060 
2061     isRGBColor: {
2062         configurable: false,
2063         enumerable: false,
2064         writable: false,
2065         value: true,
2066     }
2067 
2068 });
2069 
2070 
2071 
2072 
2073 /* Timer 定时器 
2074 
2075 parameter:
2076     func: Function; //定时器运行时的回调; 默认 null, 如果定义构造器将自动调用一次.restart()方法
2077     speed: Number; //延迟多少毫秒执行一次 func; 默认 3000;
2078     step: Integer; //执行多少次: 延迟speed毫秒执行一次 func; 默认 Infinity;
2079     
2080 attribute:
2081     func, speed, step;    //这些属性可以随时更改;
2082 
2083     //只读属性
2084     readyState: String;    //定时器状态; 可能值: '', 'start', 'running', 'done'; ''表示定时器从未启动
2085     number: Number;        //运行的次数
2086 
2087 method:
2088     start(func, speed): this;    //启动定时器 (如果定时器正在运行则什么都不会做)
2089     restart(): undefined;        //重启定时器
2090     stop(): undefined;            //停止定时器
2091 
2092 demo:
2093     //每 3000 毫秒 打印一次 timer.number, 10次后停止
2094     new Timer(timer => {
2095         console.log(timer.number);
2096         if(timer.number === 10) timer.stop();
2097     }, 3000);
2098 
2099 */
2100 class Timer{
2101 
2102     #restart = -1;
2103     #speed = 0;
2104     #isRun = false;
2105     #i = 0;
2106     #readyState = ''; //start|running
2107 
2108     get number(){
2109         return this.#i;
2110     }
2111     
2112     get readyState(){
2113         return this.#i >= this.step ? 'done' : this.#readyState;
2114     }
2115 
2116     get running(){
2117         return this.#isRun;
2118     }
2119 
2120     constructor(func = null, speed = 3000, step = Infinity){
2121         this.func = func;
2122         this.speed = speed;
2123         this.step = step;
2124         //this.onDone = null;
2125     
2126         if(typeof this.func === "function") this.restart();
2127 
2128     }
2129 
2130     start(func, time){
2131         if(typeof func === 'function') this.func = func;
2132         if(UTILS.isNumber(time) === true) this.speed = time;
2133         this.restart();
2134 
2135         return this;
2136     }
2137 
2138     restart(){
2139         if(this.#isRun === false){
2140             setTimeout(this._loop, this.speed);
2141             this.#isRun = true;
2142             this.#restart = -1;
2143             this.#i = 0;
2144             this.#readyState = 'start';
2145             
2146         }
2147 
2148         else{
2149             this.#restart = Date.now();
2150             this.#speed = this.speed;
2151 
2152         }
2153 
2154     }
2155 
2156     stop(){
2157         if(this.#isRun === true){
2158             this.#restart = -1;
2159             this.#i = this.step;
2160         }
2161 
2162     }
2163 
2164     _loop = () => {
2165 
2166         //重启计时器
2167         if(this.#restart !== -1){
2168             
2169             let gone = Date.now() - this.#restart;
2170             this.#restart = -1;
2171             
2172             if(gone >= this.#speed) gone = this.speed;
2173             else{
2174                 if(this.#speed === this.speed) gone = this.#speed - gone;
2175                 else gone = (this.#speed - gone) / this.#speed * this.speed;
2176             }
2177             
2178             setTimeout(this._loop, gone);
2179             
2180             this.#i = 1;
2181             if(this.func !== null) this.func(this);
2182 
2183         }
2184 
2185         //正在运行
2186         else if(this.#i < this.step){
2187 
2188             setTimeout(this._loop, this.speed);
2189 
2190             this.#i++;
2191             if(this.#readyState !== 'running') this.#readyState = 'running';
2192             if(this.func !== null) this.func(this);
2193 
2194         }
2195 
2196         //完成
2197         else this.#isRun = false;
2198 
2199     }
2200 
2201 }
2202 
2203 
2204 
2205 
2206 /* SeekPath A*寻路
2207 
2208 parameter: 
2209     option: Object{
2210         angle: Number,         //8 || 16
2211         timeout: Number,     //单位为毫秒
2212         size: Number,         //每格的宽高
2213         lenX, lenY: Number,    //长度
2214         disables: Array[0||1],
2215         heights: Array[Number],
2216         path: Array[], //存放寻路结果 默认创建一个空数组
2217     }
2218 
2219 attribute:
2220     size: Number;     //每个索引的大小
2221     lenX: Number;     //最大长度x (设置此属性时, 你需要重新.initMap(heights);)
2222     lenY: Number;     //最大长度y (设置此属性时, 你需要重新.initMap(heights);)
2223 
2224     //此属性已废弃 range: Box;            //本次的搜索范围, 默认: 0,0,lenX,lenY
2225     angle: Number;         //8四方向 或 16八方向 默认 16
2226     timeout: Number;     //超时毫秒 默认 500
2227     mapRange: Box;        //地图box
2228     //此属性已废弃(run函数不在检测相邻的高) maxHeight: Number;     //相邻可走的最大高 默认 6
2229 
2230     //只读属性
2231     success: Bool;            //只读; 本次搜索是否成功找到终点; (如果为false说明.run()返回的是 距终点最近的路径; 超时也会被判定为false)
2232     path: Array[x, y, z];    //存放.run()返回的路径
2233     map: Map;                 //地图的缓存数据
2234 
2235 method:
2236     initMap(heights: Array[Number]): undefiend;     //初始化类时自动调用一次; heights:如果你的场景存在高请定义此参数
2237     run(x, y, x1, y1: Number): Array[x, y, z];         //参数索引坐标
2238     getDots(x, y, a, r): Array[ix, iy];             //获取周围的点 x,y, a:8|16, r:存放结果数组
2239     getLegalPoints(ix, iy, count): Array[x, y, z];    //获取 ix, iy 周围 合法的, 相邻的 count 个点
2240 
2241 demo:
2242     const sp = new SeekPath({
2243         angle: 16,
2244         timeout: 500,
2245         //maxHeight: 6,
2246         size: 10,
2247         lenX: 1000,
2248         lenY: 1000,
2249     }),
2250 
2251     path = sp.run(0, 0, 1000, 1000);
2252 
2253     console.log(sp);
2254 
2255 */
2256 class SeekPath{
2257 
2258     static _open = []
2259     static _dots = [] //.run() .getLegalPoints()
2260     static dots4 = []; //._check()
2261     static _sort = function (a, b){return a["f"] - b["f"];}
2262 
2263     #map = null;
2264     #path = null;
2265     #success = true;
2266     #halfX = 50;
2267     #halfY = 50;
2268 
2269     #size = 10;
2270     #lenX = 10;
2271     #lenY = 10;
2272 
2273     constructor(option = {}){
2274         this.angle = (option.angle === 8 || option.angle === 16) ? option.angle : 16; //8四方向 或 16八方向
2275         this.timeout = option.timeout || 500; //超时毫秒
2276         //this.maxHeight = option.maxHeight || 6;
2277         this.mapRange = new Box();
2278         this.size = option.size || 10;
2279         this.lenX = option.lenX || 10;
2280         this.lenY = option.lenY || 10;
2281         this.#path = Array.isArray(option.path) ? option.path : [];
2282         this.initMap(option.disable, option.height);
2283         option = undefined
2284     }
2285 
2286     //this.#map[x][y] = Object{height: 此点的高,默认0, is: 此点是否可走,默认1(disable)};
2287     get map(){
2288         return this.#map;
2289     }
2290 
2291     //this.#path = Array[x,y,z]
2292     get path(){
2293         return this.#path;
2294     }
2295 
2296     get success(){
2297         return this.#success;
2298     }
2299 
2300     get size(){
2301         return this.#size;
2302     }
2303 
2304     set size(v){
2305         this.#size = v;
2306         v = v / 2;
2307         this.#halfX = v * this.#lenX;
2308         this.#halfY = v * this.#lenY;
2309     }
2310 
2311     get lenX(){
2312         return this.#lenX;
2313     }
2314 
2315     set lenX(v){
2316         this.#lenX = v;
2317         v = this.#size / 2;
2318         this.#halfX = v * this.#lenX;
2319         this.#halfY = v * this.#lenY;
2320         
2321     }
2322 
2323     get lenY(){
2324         return this.#lenY;
2325     }
2326 
2327     set lenY(v){
2328         this.#lenY = v;
2329         v = this.#size / 2;
2330         this.#halfX = v * this.#lenX;
2331         this.#halfY = v * this.#lenY;
2332         
2333     }
2334 
2335     toScene(n, v){ //n = "x|y"
2336         //n = n === "y" ? "lenY" : "lenX";
2337         if(n === "y" || n === "z") return v * this.#size - this.#halfY;
2338         return v * this.#size - this.#halfX;
2339     
2340     }
2341     
2342     toIndex(n, v){
2343         //n = n === "y" ? "lenY" : "lenX";
2344         if(n === "y" || n === "z") return Math.round((this.#halfY + v) / this.#size);
2345         return Math.round((this.#halfX + v) / this.#size);
2346 
2347     }
2348 
2349     initMap(disable, height){
2350         
2351         disable = Array.isArray(disable) === true ? disable : null;
2352         height = Array.isArray(height) === true ? height : null;
2353         
2354         const lenX = this.lenX, lenY = this.lenY;
2355         var getHeight = (ix, iy) => {
2356             if(height === null) return 0;
2357             ix = height[ix * lenY + iy];
2358             if(ix === undefined) return 0;
2359             return ix;
2360         },
2361         getDisable = (ix, iy) => {
2362             if(disable === null) return 1;
2363             ix = disable[ix * lenY + iy];
2364             if(ix === undefined) return 0;
2365             return ix;
2366         },
2367 
2368         map = []//new Map();
2369 
2370         for(let x = 0, y, m; x < lenX; x++){
2371             m = []//new Map();
2372             for(y = 0; y < lenY; y++) m[y] = {x:x, y:y, height:getHeight(x, y), is:getDisable(x, y),  g:0, h:0, f:0, p:null, id:""}//m.set(y, {x:x, y:y, height:getHeight(x, y),   g:0, h:0, f:0, p:null, id:""});
2373             map[x] = m;//map.set(x, m);
2374         }
2375         
2376         this.#map = map;
2377         this._id = -1;
2378         this._updateID();
2379         this.mapRange.set(0, 0, this.#lenX-1, this.#lenY-1);
2380 
2381         map = disable = height = getHeight = undefined;
2382 
2383     }
2384 
2385     getLegalPoints(ix, iy, count, result = []){
2386         const _dots = SeekPath._dots;
2387         result.length = 0;
2388         result[0] = this.#map[ix][iy];
2389         count += 1;
2390         
2391         while(result.length < count){
2392             for(let k = 0, i, n, d, len = result.length; k < len; k++){
2393                 n = result[k];
2394                 this.getDots(n.x, n.y, this.angle, _dots);
2395                 for(i = 0; i < this.angle; i += 2){
2396                     d = this.#map[_dots[i]][_dots[i+1]];
2397                     if(d.is === 1 && this.mapRange.containsPoint(d.x, d.y) && !result.includes(d)){
2398                         if(Math.abs(n.x - d.x) + Math.abs(n.y - d.y) === 2 && this._check(n, d)){
2399                             result.push(d);
2400                         }
2401                     }
2402                 }
2403             }
2404         }
2405     
2406         result.splice(0, 1);
2407         return result;
2408     }
2409 
2410     getLinePoints(now, next, count, result = []){
2411         if(count === 1) return result[0] = next;
2412         if(count % 2 === 0) count += 1;
2413 
2414         const len = Math.floor(count / 2), 
2415         nowPoint = UTILS.emptyPoint, 
2416         angle90 = UTILS.toAngle(90);
2417 
2418         var i, ix, iy, n, nn = next;
2419 
2420         nowPoint.set(now.x, now.y).rotate(next, angle90); //now 以 next 为原点顺时针旋转 90 度
2421         var disX = nowPoint.x - next.x, 
2422         disY = nowPoint.y - next.y;
2423         
2424         for(i = 0; i < len; i++){
2425             ix = disX + disX * i + next.x;
2426             iy = disY + disY * i + next.y;
2427 
2428             n = this.#map[ix][iy];
2429             if(n.is === 1) nn = n;
2430             result[len-1-i] = nn;
2431         }
2432 
2433         result[len] = next;
2434         nn = next
2435 
2436         nowPoint.set(now.x, now.y).rotate(next, -angle90);
2437         disX = nowPoint.x - next.x; 
2438         disY = nowPoint.y - next.y;
2439 
2440         for(i = 0; i < len; i++){
2441             ix = disX + disX * i + next.x;
2442             iy = disY + disY * i + next.y;
2443 
2444             n = this.#map[ix][iy];
2445             if(n.is === 1) nn = n;
2446             result[len+1+i] = nn;
2447         }
2448 
2449         return result;
2450     }
2451 
2452     getDots(x, y, a, r = []){ //获取周围的点 x,y, a:8|16, r:存放结果数组
2453         r.length = 0;
2454         const x_1 = x-1, x1 = x+1, y_1 = y-1, y1 = y+1;
2455         if(a === 16) r.push(x_1, y_1, x, y_1, x1, y_1, x_1, y, x1, y, x_1, y1, x, y1, x1, y1);
2456         else r.push(x, y_1, x, y1, x_1, y, x1, y);
2457     }
2458 
2459     _updateID(){ //更新标记
2460         this._id++;
2461         this._openID = "o_"+this._id;
2462         this._closeID = "c_"+this._id;
2463     }
2464 
2465     _check(dotA, dotB){ //检测 a 是否能到 b
2466         //获取 dotB 周围的4个点 并 遍历这4个点
2467         this.getDots(dotB.x, dotB.y, 8, SeekPath.dots4);
2468         for(let k = 0, x, y; k < 8; k += 2){
2469             x = SeekPath.dots4[k]; 
2470             y = SeekPath.dots4[k+1];
2471             if(this.mapRange.containsPoint(x, y) === false) continue;
2472 
2473             //找出 dotA 与 dotB 相交的两个点:
2474             if(Math.abs(dotA.x - x) + Math.abs(dotA.y - y) === 1){
2475                 //如果其中一个交点是不可走的则 dotA 到 dotB 不可走, 既返回 false
2476                 if(this.#map[x][y].is === 0) return false;
2477             }
2478 
2479         }
2480 
2481         return true;
2482     }
2483 
2484     run(x, y, x1, y1, path = this.#path){
2485         path.length = 0;
2486         if(this.#map === null || this.mapRange.containsPoint(x, y) === false) return path;
2487         
2488         var _n = this.#map[x][y];
2489         if(_n.is === 0) return path;
2490 
2491         const _sort = SeekPath._sort,
2492         _open = SeekPath._open,
2493         _dots = SeekPath._dots, 
2494         time = Date.now();
2495 
2496         //var isDot = true, 
2497         var suc = _n, k, mhd, g, h, f, _d;
2498 
2499         _n.g = 0;
2500         _n.h = _n.h = Math.abs(x1 - x) * 10 + Math.abs(y1 - y) * 10; 
2501         _n.f = _n.h;
2502         _n.p = null;
2503         this._updateID();
2504         _n.id = this._openID;
2505         _open.push(_n);
2506         
2507         while(_open.length !== 0){
2508             if(Date.now() - time > this.timeout) break;
2509 
2510             _open.sort(_sort);
2511             _n = _open.shift();
2512             if(_n.x === x1 && _n.y === y1){
2513                 suc = _n;
2514                 break;
2515             }
2516             
2517             if(suc.h > _n.h) suc = _n;
2518             _n.id = this._closeID;
2519             this.getDots(_n.x, _n.y, this.angle, _dots);
2520             
2521             for(k = 0; k < this.angle; k += 2){
2522                 
2523                 _d = this.#map[_dots[k]][_dots[k+1]];
2524                 if(_d.id === this._closeID || _d.is === 0 || this.mapRange.containsPoint(_d.x, _d.y) === false) continue;
2525 
2526                 mhd = Math["abs"](_n["x"] - _d.x) + Math["abs"](_n["y"] - _d.y);
2527                 g = _n["g"] + (mhd === 1 ? 10 : 14);
2528                 h = Math["abs"](x1 - _d.x) * 10 + Math["abs"](y1 - _d.y) * 10;
2529                 f = g + h;
2530             
2531                 if(_d.id !== this._openID){
2532                     //如果是斜角和8方向:
2533                     if(mhd !== 1 && this.angle === 16){
2534                         if(this._check(_n, _d)){
2535                             _d.g = g;
2536                             _d.h = h;
2537                             _d.f = f;
2538                             _d.p = _n;
2539                             _d.id = this._openID;
2540                             _open.push(_d);
2541                         }
2542                     }else{
2543                         _d.g = g;
2544                         _d.h = h;
2545                         _d.f = f;
2546                         _d.p = _n;
2547                         _d.id = this._openID;
2548                         _open.push(_d);
2549                     }
2550                 }
2551 
2552                 else if(g < _d.g){
2553                     _d.g = g;
2554                     _d.f = g + _d.h;
2555                     _d.p = _n;
2556                 }
2557     
2558             }
2559         }
2560 
2561         this.#success = suc === _n;
2562 
2563         while(suc !== null){
2564             path.unshift(suc);
2565             //path.unshift(this.toScene("x", suc["x"]), suc["height"], this.toScene("y", suc["y"]));
2566             suc = suc["p"];
2567         }
2568 
2569         _open.length = _dots.length = 0;
2570         
2571         return path;
2572     }
2573 
2574 }
2575 
2576 
2577 
2578 
2579 /* RunningList
2580     如果触发过程中删除(回调函数中删除正在遍历的数组), 不仅 len 没有变(遍历前定义的len没有变, 真实的len随之减少), 而且还会漏掉一个key;
2581 
2582 */
2583 class RunningList{
2584 
2585     /* static getProxy(runName){
2586 
2587         return new Proxy(new RunningList(runName), {
2588 
2589             get(tar, key){
2590                 
2591             },
2592 
2593             set(tar, key, val){
2594                 
2595             }
2596             
2597         });
2598 
2599     } */
2600 
2601     constructor(runName = 'update'){
2602         this._running = false;
2603         this._list = [];
2604         this._delList = [];
2605         this._runName = runName;
2606     }
2607 
2608     get length(){
2609 
2610         return this._list.length;
2611 
2612     }
2613 
2614     add(v){
2615 
2616         if(!this._list.includes(v)) this._list.push(v);
2617 
2618     }
2619 
2620     clear(){
2621         this._list.length = 0;
2622         this._delList.length = 0;
2623     }
2624 
2625     push(...v){
2626 
2627         v.forEach(_v => this._list.push(_v));
2628 
2629     }
2630 
2631     splice(v){
2632         if(this._running === true){
2633             if(!this._delList.includes(v)) this._delList.push(v);
2634         }
2635 
2636         else{
2637             const i = this._list.indexOf(v);
2638             if(i !== -1) this._list.splice(i, 1);
2639         }
2640 
2641     }
2642 
2643     update(){
2644 
2645         var k, len = this._list.length;
2646 
2647         this._running = true;
2648         if(this._runName !== ''){
2649             for(k = 0; k < len; k++) this._list[k][this._runName]();
2650         }else{
2651             for(k = 0; k < len; k++) this._list[k]();
2652         }
2653         this._running = false;
2654 
2655         var i;
2656         len = this._delList.length;
2657         for(k = 0; k < len; k++){
2658             //this.splice(this._delList[k]);
2659             i = this._list.indexOf(this._delList[k]);
2660             if(i !== -1) this._list.splice(i, 1);
2661         }
2662         this._delList.length = 0;
2663         
2664     }
2665 
2666 }
2667 
2668 
2669 
2670 
2671 /* TweenValue (从 原点 以规定的时间到达  终点)
2672 
2673 parameter: origin, end, time, onUpdate, onEnd;
2674 
2675 attribute:
2676     origin: Object; //原点(起点)
2677     end: Object; //终点
2678     time: Number; //origin 到 end 花费的时间
2679     onUpdate: Function; //更新回调; 一个回调参数 origin; 默认null;
2680     onEnd: Function; //结束回调; 没有回调参数; 默认null; (如果返回的是"restart"将不从队列删除, 你可以在onEnd中更新.end不间断的继续补间)
2681 
2682 method:
2683     reset(origin, end: Object): undefined; //更换 .origin, .end; 它会清除其它对象的缓存属性
2684     reverse(): undefined; //this.end 复制 this.origin 的原始值
2685     update(): undefined; //Tween 通过此方法统一更新 TweenValue
2686 
2687 demo: 
2688     //init Tween:
2689     const tween = new Tween(),
2690     animate = function (){
2691         requestAnimationFrame(animate);
2692         tween.update();
2693     }
2694 
2695     //init TweenValue:
2696     const v1 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v));
2697     
2698     animate();
2699     tween.start(v1);
2700 
2701 */
2702 class TweenValue{
2703 
2704     constructor(origin = {}, end = {}, time = 500, onUpdate = null, onEnd = null, onStart = null){
2705         this.origin = origin;
2706         this.end = end;
2707         this.time = time;
2708 
2709         this.onUpdate = onUpdate;
2710         this.onEnd = onEnd;
2711         this.onStart = onStart;
2712         
2713         //以下属性不能直接设置
2714         this._r = null;
2715         this._t = 0;
2716         this._v = Object.create(null);
2717 
2718     }
2719 
2720     _start(){
2721         var v = "";
2722         for(v in this.origin) this._v[v] = this.origin[v];
2723 
2724         this._t = Date.now();
2725         //this.update();
2726 
2727     }
2728 
2729     reset(origin, end){
2730         this.origin = origin;
2731         this.end = end;
2732         this._v = Object.create(null);
2733 
2734     }
2735 
2736     reverse(){
2737         var n = "";
2738         for(n in this.origin) this.end[n] = this._v[n];
2739 
2740     }
2741 
2742     update(){
2743 
2744         if(this["_r"] !== null){
2745 
2746             var ted = Date["now"]() - this["_t"];
2747 
2748             if(ted >= this["time"]){
2749 
2750                 for(ted in this["origin"]) this["origin"][ted] = this["end"][ted];
2751 
2752                 if(this["onEnd"] !== null){
2753 
2754                     if(this["onEnd"](this) === "restart"){
2755                         if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
2756                         this["_start"]();
2757                     }
2758 
2759                     else this["_r"]["stop"](this);
2760                     
2761                 }
2762 
2763                 else this["_r"]["stop"](this);
2764 
2765             }
2766 
2767             else{
2768                 ted = ted / this["time"];
2769                 let n = "";
2770                 for(n in this["origin"]) this["origin"][n] = ted * (this["end"][n] - this["_v"][n]) + this["_v"][n];
2771                 if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
2772             }
2773 
2774         }
2775 
2776     }
2777 
2778 }
2779 
2780 Object.defineProperties(TweenValue.prototype, {
2781 
2782     isTweenValue: {
2783         configurable: false,
2784         enumerable: false,
2785         writable: false,
2786         value: true,
2787     }
2788 
2789 });
2790 
2791 
2792 
2793 
2794 /* TweenAlone (相对于 TweenValue 此类可以独立补间, 不需要 Tween)
2795 
2796 demo:
2797     const v1 = new TweenAlone({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v)),
2798     animate = function (){
2799         requestAnimationFrame(animate);
2800         v1.update();
2801     }
2802 
2803     animate();
2804     v1.start();
2805 
2806 */
2807 class TweenAlone extends TweenValue{
2808 
2809     constructor(origin, end, time, onUpdate, onEnd, onStart){
2810         super(origin, end, time, onUpdate, onEnd, onStart);
2811         
2812     }
2813 
2814     start(){
2815         if(this.onStart !== null) this.onStart();
2816         this._r = this;
2817         this._start();
2818 
2819     }
2820 
2821     stop(){
2822         this._r = null;
2823         
2824     }
2825 
2826 }
2827 
2828 
2829 
2830 
2831 
2832 /* Tween 动画补间 (TweenValue 的root, 可以管理多个TweenValue)
2833 
2834 parameter:
2835 attribute:
2836 method:
2837     start(value: TweenValue): undefined;
2838     stop(value: TweenValue): undefined;
2839 
2840 static:
2841     Value: TweenValue;
2842 
2843 demo:
2844     //init Tween:
2845     const tween = new Tween(),
2846     animate = function (){
2847         requestAnimationFrame(animate);
2848         tween.update();
2849     }
2850 
2851     //init TweenValue:
2852     const v2 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v), v => {
2853         v2.reverse(); //v2.end 复制起始值
2854         return "restart"; //返回"restart"表示不删除队列, 需要继续补间
2855     });
2856     
2857     animate();
2858     tween.start(v2);
2859 
2860 */
2861 class Tween extends RunningList{
2862 
2863     static Value = TweenValue;
2864 
2865     constructor(){
2866         super();
2867 
2868     }
2869 
2870     start(value){
2871         if(value.onStart !== null) value.onStart();
2872         if(value._r === null) this.push(value);
2873         value._r = this;
2874         value._start(this);
2875 
2876     }
2877 
2878     stop(value){
2879         if(value._r !== null) this.splice(value);
2880         value._r = null;
2881         
2882     }
2883 
2884 }
2885 
2886 
2887 
2888 
2889 /* TweenTarget 朝着轴插值(有效的跟踪动态目标, 注意此类需要配合 RunningList 类使用, 因为此类在任何情况下都没有阻止你调用.update()方法)
2890 parameter:    
2891     v1 = {x: 0}, 
2892     v2 = {x: 100}, 
2893     distance = 1,        //每次移动的距离
2894     onUpdate = null,    //
2895     onEnd = null
2896 
2897 attribute:
2898     v1: Object;             //起点
2899     v2: Object;             //终点
2900     onUpdate: Function;        //
2901     onEnd: Function;         //
2902 
2903 method:
2904     update(): undefined;                        //一般在动画循环里执行此方法
2905     updateAxis(): undefined;                     //更新v1至v2的方向轴 (初始化时构造器自动调用一次)
2906     setDistance(distance: Number): undefined;     //设置每次移动的距离 (初始化时构造器自动调用一次)
2907 
2908 demo:
2909     const ttc = new TweenTarget({x:0, y:0}, {x:100, y:100}, 10),
2910 
2911     //计时器模拟动画循环函数, 每秒执行一次.update()
2912     timer = new Timer(() => {
2913         ttc.update();
2914         console.log('update: ', ttc.v1);
2915 
2916     }, 1000, Infinity);
2917 
2918     ttc.onEnd = v => {
2919         timer.stop();
2920         console.log('end: ', v);
2921     }
2922 
2923     timer.start();
2924     console.log(ttc);
2925 
2926 */
2927 class TweenTarget{
2928 
2929     #distance = 1;
2930     #distancePow2 = 1;
2931     #axis = {};
2932 
2933     constructor(v1 = {x: 0}, v2 = {x: 100}, distance, onUpdate = null, onEnd = null){
2934         this.v1 = v1;
2935         this.v2 = v2;
2936         this.onUpdate = onUpdate;
2937         this.onEnd = onEnd;
2938     
2939         this.setDistance(distance);
2940         this.updateAxis();
2941     }
2942 
2943     setDistance(v = 1){
2944         this.#distance = v;
2945         this.#distancePow2 = Math.pow(v, 2);
2946     }
2947 
2948     updateAxis(){
2949         var n, v, len = 0;
2950 
2951         for(n in this.v1){
2952             v = this.v2[n] - this.v1[n];
2953             len += v * v;
2954             this.#axis[n] = v;
2955 
2956         }
2957 
2958         len = Math.sqrt(len);
2959 
2960         if(len !== 0){
2961             
2962             for(n in this.v1) this.#axis[n] *= 1 / len;
2963 
2964         }
2965     }
2966 
2967     update(){
2968         var n, len = 0;
2969 
2970         for(n in this.v1) len += Math.pow(this.v1[n] - this.v2[n], 2);
2971 
2972         if(len > this.#distancePow2){
2973 
2974             for(n in this.v1) this.v1[n] += this.#axis[n] * this.#distance;
2975             if(this.onUpdate !== null) this.onUpdate(this.v1);
2976 
2977         }
2978 
2979         else{
2980 
2981             for(n in this.v1) this.v1[n] = this.v2[n];
2982             if(this.onEnd !== null) this.onEnd(this.v1);
2983             
2984         }
2985     }
2986 
2987 }
2988 
2989 
2990 
2991 
2992 /* EventDispatcher 自定义事件管理器
2993 parameter: 
2994 attribute: 
2995 
2996 method:
2997     clearEvents(eventName): undefined;             //清除eventName列表, 如果 eventName 未定义清除所有事件
2998     customEvents(eventName, eventParam): this;    //创建自定义事件 eventParam 可选 默认{}
2999     getParam(eventName): eventParam;
3000     trigger(eventName, callback): undefined;    //触发 (callback: 可选)
3001     register(eventName, callback): undefined;    //
3002     deleteEvent(eventName, callback): undefined; //
3003 
3004 demo:
3005     const eventDispatcher = new EventDispatcher();
3006     eventDispatcher.customEvents("test", {name: "test"});
3007 
3008     eventDispatcher.register("test", eventParam => {
3009         console.log(eventParam) //Object{name: "test"}
3010     });
3011 
3012     eventDispatcher.trigger("test");
3013 
3014 */
3015 class EventDispatcher{
3016     
3017     constructor(){
3018         this._eventsList = {};
3019         this.__eventsList = [];
3020         this.__trigger = "";
3021 
3022     }
3023 
3024     clearEvents(eventName){ 
3025 
3026         //if(this.__trigger === eventName) return console.warn("EventDispatcher: 清除事件失败");
3027         if(this._eventsList[eventName] !== undefined) this._eventsList[eventName].func = []
3028 
3029         else this._eventsList = {}
3030 
3031     }
3032     
3033     customEvents(eventName, eventParam = {}){ 
3034         //if(typeof eventName !== "string") return console.warn("EventDispatcher: 注册自定义事件失败");
3035         if(this._eventsList[eventName] !== undefined) return console.warn("EventDispatcher: "+eventName+" 已存在");
3036         //eventParam = eventParam || {}
3037         //if(eventParam.type === undefined) eventParam.type = eventName;
3038         this._eventsList[eventName] = {func: [], param: eventParam}
3039         return this;
3040     }
3041 
3042     getParam(eventName){
3043         return this._eventsList[eventName]["param"];
3044     }
3045     
3046     trigger(eventName, callback){
3047         //if(this._eventsList[eventName] === undefined) return;
3048         
3049         const obj = this._eventsList[eventName];
3050         var k, len = obj.func.length;
3051 
3052         if(len !== 0){
3053             if(typeof callback === "function") callback(obj["param"]); //更新参数(eventParam)
3054 
3055             //触发过程(如果触发过程中删除事件, 不仅 len 没有变, 而且还会漏掉一个key, 所以在触发过程中删除的事件要特殊处理)
3056             this.__trigger = eventName;
3057             for(k = 0; k < len; k++) obj["func"][k](obj["param"]);
3058             this.__trigger = "";
3059             //触发过程结束
3060             
3061             //删除在触发过程中要删除的事件
3062             len = this.__eventsList.length;
3063             for(k = 0; k < len; k++) this.deleteEvent(eventName, this.__eventsList[k]);
3064             this.__eventsList.length = 0;
3065     
3066         }
3067 
3068     }
3069     
3070     register(eventName, callback){
3071         if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
3072         const obj = this._eventsList[eventName];
3073         //if(obj.func.includes(callback) === false) obj.func.push(callback);
3074         //else console.warn("EventDispatcher: 回调函数重复");
3075         obj.func.push(callback);
3076     }
3077     
3078     deleteEvent(eventName, callback){
3079         if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
3080         
3081         if(this.__trigger === eventName) this.__eventsList.push(callback);
3082         else{
3083             const obj = this._eventsList[eventName], i = obj.func.indexOf(callback);
3084             if(i !== -1) obj.func.splice(i, 1);
3085         }
3086         
3087     }
3088 
3089 }
3090 
3091 
3092 
3093 
3094 export {
3095     UTILS, 
3096     ColorRefTable, 
3097     Ajax, 
3098     IndexedDB, 
3099     TreeStruct, 
3100     Point, 
3101     Line, 
3102     Box, 
3103     Circle, 
3104     Polygon, 
3105     RGBColor, 
3106     Timer, 
3107     SeekPath, 
3108     RunningList, 
3109     TweenValue, 
3110     TweenAlone, 
3111     Tween, 
3112     TweenTarget, 
3113     EventDispatcher, 
3114     Rotate,
3115     ShapeRect,
3116 }
Utils.js

 

Canvas 实用类库:

   1 "use strict";
   2 
   3 import {
   4     UTILS, 
   5     Box, 
   6     Rotate,
   7 } from './Utils.js';
   8 
   9 
  10 /* CanvasImageEvent
  11 parameter:
  12     domElement: CanvasImageRender.domElement; //必须
  13     box: CanvasImageRender.domElementRect; //必须
  14     ||或者第一个参数为 CanvasImageRender
  15 
  16     与.initEvent(domElement, box)参数一样
  17 
  18 attribute:
  19     domElement //CanvasImageRender.domElement
  20     box: Box; //忽略box以外的ca, 通常为 CanvasImageRender.domElementRect 的引用
  21 
  22 method:
  23     add(ca: CanvasImage, eventName: String, callback: Function): ca; //ca添加事件
  24     remove(ca: CanvasImage, eventName: String, callback: Function): ca; //ca删除事件
  25         eventName: 可能的值为 CanvasImageEvent.canvasEventsList 的属性名
  26         callback: 参数 event, ca
  27     
  28     clear(ca: CanvasImage, eventName: String): ca; //ca 必须; eventName 可选, 如果未定义则清空ca的所有事件;
  29     disposeEvent(eventName): this; //.initEvent(domElement, box)调用一次此方法
  30     initEvent(domElement, box): this; //每次更换 domElement, box 后应调用此方法; (CanvasAnimateEvent初始化时自动调用一次)
  31     setScale(x, y: Number): undefiend; //
  32 
  33 event:
  34     (如果注册了 "out"|"over" 事件, 在弃用时(CanvasImageRender 不在使用): 
  35     必需要调用.disposeEvent(eventName)方法清理注册的dom事件, 
  36     因为这两个事件用的是 pointermove 而不是 onpointermove);
  37 
  38     CanvasImageEvent.canvasEventsList
  39 
  40 demo:
  41     const car = new CanvasImageRender({width: 100, height: 100}),
  42     cae = new CanvasImageEvent(car),
  43     ca = car.add(new CanvasImage(image));
  44 
  45     //ca添加点击事件
  46     cae.add(ca, 'click', (event, target) => console.log(event, target));
  47 
  48     car.render();
  49 
  50 */
  51 class CanvasImageEvent{
  52 
  53     static bind(obj, is){
  54         obj._eventList = {}
  55 
  56         if(is === true){
  57             let k, evns = CanvasImageEvent.canvasEventsList;
  58             for(k in evns) obj._eventList[k] = [];
  59         }
  60             
  61     }
  62 
  63     static canvasEventsList = {
  64         down: "onpointerdown",
  65         move: "onpointermove",
  66         up: "onpointerup",
  67         click: "onclick",
  68         wheel: "onmousewheel",
  69         out: "pointermove", //移出 
  70         over: "pointermove", //移入
  71     }
  72 
  73     static isEventName(eventName){
  74 
  75         return CanvasImageEvent.canvasEventsList[eventName] !== undefined;
  76 
  77     }
  78     
  79     static emptyBox = new Box();
  80     static emptyRotate = new Rotate();
  81 
  82     constructor(domElement, box){
  83         this._running = "";
  84         this._delList = [];
  85         this.initEvent(domElement, box);
  86         CanvasImageEvent.bind(this);
  87     }
  88 
  89     initEvent(domElement, box){
  90         this.disposeEvent();
  91 
  92         if(CanvasImageRender.prototype.isPrototypeOf(domElement)){
  93             this.domElement = domElement.domElement;
  94             this.box = domElement.domElementRect;
  95         }
  96 
  97         else{
  98             this.domElement = domElement;
  99             this.box = box;
 100         }
 101 
 102         if(this._eventList !== undefined){
 103             for(let evn in this._eventList){
 104                 if(this._eventList[evn] !== undefined) this._createEvent(evn);
 105             }
 106 
 107         }
 108         
 109         return this;
 110     }
 111 
 112     add(ca, eventName, callback){
 113         if(CanvasImageEvent.canvasEventsList[eventName] === undefined) return console.warn("CanvasImageEvent: 参数错误 "+ eventName);
 114         if(typeof callback !== "function") return console.warn("CanvasImageEvent: 事件添加失败,参数错误");
 115         
 116         this._add(ca, eventName);
 117         this._addCA(ca, eventName, callback);
 118         
 119         return ca;
 120     }
 121     
 122     remove(ca, eventName, callback){
 123         if(CanvasImageEvent.canvasEventsList[eventName] === undefined) return console.warn("CanvasImageEvent: 参数错误 "+ eventName);
 124         if(typeof callback !== "function") return console.warn("CanvasImageEvent: 事件添加失败,参数错误");
 125         if(this._running !== eventName){
 126             this._remove(ca, eventName);
 127             this._removeCA(ca, eventName, callback);
 128         }
 129 
 130         else this._delList.push(ca, eventName, callback);
 131 
 132         return ca;
 133     }
 134 
 135     disposeEvent(eventName){
 136         if(!this.domElement) return this;
 137         
 138         if(eventName === "over" || eventName === "out"){
 139 
 140             if(typeof this["_"+eventName] === "function"){
 141                 this.domElement.removeEventListener(CanvasImageEvent.canvasEventsList[eventName], this["_"+eventName]);
 142                 delete this["_"+eventName];
 143             }
 144 
 145         }
 146 
 147         else{
 148 
 149             if(typeof this["_over"] === "function"){
 150                 this.domElement.removeEventListener(CanvasImageEvent.canvasEventsList["over"], this["_over"]);
 151                 delete this["_over"];
 152             }
 153 
 154             if(typeof this["_out"] === "function"){
 155                 this.domElement.removeEventListener(CanvasImageEvent.canvasEventsList["out"], this["_out"]);
 156                 delete this["_out"];
 157             }
 158 
 159         }
 160 
 161         return this;
 162     }
 163     
 164     clear(ca, eventName){
 165         if(eventName === undefined){
 166             var k; for(k in this._eventList){
 167                 this._remove(ca, k);
 168             }
 169             
 170             if(ca._eventList !== undefined) delete ca._eventList; //CanvasImageEvent.bind(ca, true);
 171             
 172         }
 173 
 174         else if(CanvasImageEvent.canvasEventsList[eventName] !== undefined){
 175             this._remove(ca, eventName);
 176 
 177             if(ca._eventList !== undefined) ca._eventList[eventName].length = 0;
 178             
 179         }
 180 
 181         return ca;
 182     }
 183 
 184     _addCA(ca, eventName, callback){
 185         if(ca._eventList === undefined) CanvasImageEvent.bind(ca);
 186         if(ca._eventList[eventName] === undefined) ca._eventList[eventName] = [];
 187         ca._eventList[eventName].push(callback);
 188 
 189     }
 190 
 191     _removeCA(ca, eventName, callback){
 192         if(ca._eventList !== undefined && ca._eventList[eventName] !== undefined){
 193             for(let k = 0, len = ca._eventList[eventName].length; k < len; k++){
 194                 if(ca._eventList[eventName][k] === callback){
 195                     ca._eventList[eventName].splice(k, 1);
 196                     break;
 197                 }
 198             }
 199         }
 200 
 201     }
 202 
 203     _add(ca, eventName){
 204         if(this._eventList[eventName] === undefined){
 205             this._eventList[eventName] = [];
 206             this._createEvent(eventName);
 207 
 208         }
 209 
 210         //if(this._eventList[eventName].includes(ca) === false) this._eventList[eventName].push(ca);
 211         this._eventList[eventName].push(ca);
 212     }
 213 
 214     _remove(ca, eventName){
 215         if(this._eventList[eventName] !== undefined){
 216             let key = this._eventList[eventName].indexOf(ca);
 217             if(key !== -1) this._eventList[eventName].splice(key, 1);
 218             if(key === 0){
 219                 if(eventName == "over" || eventName === "out") this.disposeEvent(eventName);
 220                 else this.domElement[CanvasImageEvent.canvasEventsList[eventName]] = null;
 221                 delete this._eventList[eventName];
 222             }
 223 
 224         }
 225 
 226     }
 227 
 228     _createEvent(evn){
 229         var k, len, ca, arr, tar = null, oldTar = null, _run = null;
 230 
 231         const _box = CanvasImageEvent.emptyBox, _rotate = CanvasImageEvent.emptyRotate,
 232 
 233         run = event => {
 234             len = this["_eventList"][evn].length;
 235             
 236             if(len !== 0 && this.box.containsPoint(event.pageX, event.pageY)){
 237                 tar = null;
 238                 for(k = 0; k < len; k++){
 239                     ca = this["_eventList"][evn][k];
 240                     _box.copy(ca.box);
 241                     
 242                     //计算旋转后的box
 243                     if(ca.rotate !== null) _box.setFromRotate(_rotate.set(ca.x+ca.rotate.rotate.origin.x, ca.y+ca.rotate.origin.y, ca.rotate.angle));
 244 
 245                     if(ca["visible"] === true && _box.containsPoint(event["pageX"] - this["box"]["x"], event["pageY"] - this["box"]["y"])){
 246                         
 247                         if(tar === null || tar["index"] < ca["index"]) tar = ca;
 248                         
 249                     }
 250     
 251                 }
 252                 
 253                 if(_run !== null) _run();
 254                 if(tar !== null){
 255                     this._running = evn;
 256                     arr = tar["_eventList"][evn]; 
 257                     len = arr.length;
 258                     for(k = 0; k < len; k++) arr[k](event, tar);
 259                     tar = null;
 260 
 261                     len = this._delList.length;
 262                     for(k = 0; k < len; k += 3){
 263                         this._remove(this._delList[k], this._delList[k+1]);
 264                         this._removeCA(this._delList[k], this._delList[k+1], this._delList[k+2]);
 265                     }
 266                     this._running = "";
 267                     this._delList.length = 0;
 268                 }
 269 
 270             }
 271             
 272         }
 273 
 274         if(evn === "over" || evn === "out"){
 275             this.domElement.addEventListener(CanvasImageEvent.canvasEventsList[evn], run);
 276             this["_"+evn] = run;
 277             if(evn === "over"){
 278                 _run = ()=>{
 279                     if(tar !== null){
 280                         if(oldTar !== null){
 281                             if(oldTar !== tar) oldTar = tar;
 282                             else tar = null;
 283                         }
 284                         else oldTar = tar;
 285                     }
 286                     else if(oldTar !== null) oldTar = null;
 287     
 288                 }
 289 
 290             }
 291 
 292             else{
 293                 let _tar = null;
 294                 _run = ()=>{
 295                     if(tar !== null){
 296                         if(oldTar !== null){
 297                             if(oldTar !== tar){
 298                                 _tar = tar;
 299                                 tar = oldTar;
 300                                 oldTar = _tar;
 301                             }
 302                             else tar = null;
 303                         }
 304                         else{
 305                             oldTar = tar;
 306                             tar = null;
 307                         }
 308                     }
 309                     else if(oldTar !== null){
 310                         tar = oldTar;
 311                         oldTar = null;
 312                     }
 313     
 314                 }
 315 
 316             }
 317 
 318             /* _run = ()=>{
 319                 if(tar !== null){
 320                     if(oldTar !== null){
 321 
 322                         if(oldTar !== tar){
 323                             if(evn === "over") oldTar = tar;
 324                             else{
 325                                 let _tar = tar;
 326                                 tar = oldTar;
 327                                 oldTar = _tar;
 328                             }
 329                         }
 330 
 331                         else tar = null;
 332 
 333                     }
 334 
 335                     else{
 336                         oldTar = tar;
 337                         if(evn === "out") tar = null;
 338                         
 339                     }
 340                     
 341                 }
 342 
 343                 else{
 344                     if(oldTar !== null){
 345                         if(evn === "out") tar = oldTar;
 346                         oldTar = null;
 347                     }
 348                     
 349                 }
 350 
 351             } */
 352             
 353         }
 354 
 355         else this.domElement[CanvasImageEvent.canvasEventsList[evn]] = run;
 356 
 357     }
 358 
 359 }
 360 
 361 
 362 
 363 
 364 /* CanvasImageRender (渲染 CanvasImage)
 365 注意:
 366     this.box = new Box();                 //本类绘制时用这box检测 (ca的box是否与这个box相交, 如果相交才有可能绘制这个ca)
 367     this.domElementRect = new Box();     //CanvasImageEvent 用这个box检测 (鼠标的位置是否在这个box范围内, 如果在才有可能触发这个ca的事件)
 368 
 369 ImageData:
 370     length: w * h * 4
 371     r: 0 - 255
 372     g: 0 - 255
 373     b: 0 - 255
 374     a: 0 - 255 Math.round(255 * a)
 375     遍历:
 376         const data = context.getImageData(0, 0, canvas.width, canvas.height).data;
 377         for(let k = 0, x, y, r, g, b, a, i; k < len; k++){
 378             x = k % width;
 379             y = Math.floor(k / width);
 380 
 381             i = k*4;
 382             r = data[i]
 383             g = data[i+1]
 384             b = data[i+2]
 385             a = data[i+3]
 386 
 387             console.log(x, y, r, g, b, a, i);
 388         }
 389 
 390 parameter: 
 391     option = {
 392         canvas //默认新的canvas
 393         width, height //默认1
 394         id
 395     }
 396 
 397 attribute:
 398     list: Array[CanvasImage]; //渲染队列
 399     box: Box; //canvas的位置和范围(.x.y是0; .w.h是canvas的宽高, 在渲染时检测ca的box是否与此box相交);
 400     context: CanvasContext;
 401     domElement: Canvas;
 402 
 403 method:
 404     isDraw(ca): Bool; //ca是否满足绘制条件
 405     isCanvasImage(img: Object): Bool; //img是否是canvas可绘制的图片;
 406     add(ca): ca; //添加ca (添加多个: const len = this.list.length; this.list.push(caA, caB); this.initList(len))
 407     remove(ca): ca; //删除ca (删除多个: this.list.splice(i, len); this.initList(i));
 408     updateCanvas(): this; //更新canvas的box, canvas添加到dom树后才有效; (如果你没有用.pos(x, y)和.size(w, h, setElem)设置canvas的位置和宽高的话, 那么你需要调用一次此方法, 否则 事件错误 等异常)
 409     initList(index): this; //初始化ca列表; (如果你没有用.add(ca)添加 或 .remove(cd)删除 那么你需要调用一次此方法, 否则 删除错误, 事件错误 等异常)
 410     pos(x, y): this; //设置canvas的位置和this.box的位置
 411     size(w, h: Number, setElem: Bool): this; //设置this.box的宽高; setElem: 是否同时设置canvas的宽高; 默认true
 412     render(parentElem): this; //绘制一次画布, 并把画布添加到parentElem, parentElem 默认 body, 然后调用一次 this.updateCanvas() 方法;
 413     clear(): undefiend; //清除画布
 414     draw(): undefiend; //绘制画布
 415     redraw(): this; //清除并绘制画布; 一般在动画循环里面调用此方法
 416     clearTarget(ca): undefiend; //清除 ca; 适应静态视图
 417     drawTarget(ca): undefiend; //绘制 ca; 适应静态视图 
 418     computeOverlaps(ca)
 419 
 420     //以下方法即将弃用
 421     shear(ca, canvas, x, y): canvas; //this.canvas 的 ca 范围剪切到 canvas 的 x,y位置; ca默认this; canvas模型新的canvas; x, y默认0;
 422     getData(box)
 423     putData(data, x, y)
 424 
 425     //参数名:
 426     ca: CanvasImage; 包括它的子类
 427     box: Box;
 428 
 429 demo:
 430 
 431     //更新ca.box(注意执行顺序, 这很重要):
 432     root.clearTarget(ca);
 433     ca.box.set(10, 50, 100, 100);
 434     root.drawTarget(ca);
 435 
 436     //显示:
 437     ca.visible = false;
 438     root.clearTarget(ca);
 439 
 440     ca.visible = true;
 441     root.drawTarget(ca);
 442 
 443     //或者定义一个空的ca:
 444     const emptyCA = new CanvasImage();
 445     ca.visible = true;
 446     emptyCA.box.copy(ca.box);
 447     root.drawTarget(ca);
 448 
 449 以以上方法绘制ca的利与弊:
 450     利: 比.redraw()效率更高; .redraw()每次绘制全部可绘制的ca, 而此方法每次只绘制与ca.box重叠的ca数;
 451     弊: 如果目标ca的box覆盖了整个canvas那么像上面这样绘制反而会比.redraw()慢(因为多了一步计算重叠的时间);
 452 
 453 更多例子: class CanvasAnimateUI 使用此方法更新画布
 454 
 455 */
 456 class CanvasImageRender{
 457 
 458     static emptyArrA = []
 459     static emptyArrB = []
 460     static paramCon = {alpha: true}
 461 
 462     static defaultStyles = {
 463         filter: "none",
 464         globalAlpha: 1,
 465         globalCompositeOperation: "source-over",
 466         imageSmoothingEnabled: true,
 467         miterLimit: 10,
 468         font: "12px SimSun, Songti SC",
 469         textAlign: "left",
 470         textBaseline: "top",
 471         lineCap: "butt",
 472         lineJoin: "miter",
 473         lineDashOffset: 0,
 474         lineWidth: 1,
 475         shadowColor: "rgba(0, 0, 0, 0)",
 476         shadowBlur: 0,
 477         shadowOffsetX: 0,
 478         shadowOffsetY: 0,
 479         fillStyle: "#000000",
 480         strokeStyle: "#ffffff",
 481     }
 482 
 483     static setDefaultStyles(context){
 484         let k = "", styles = CanvasImageRender.defaultStyles;
 485         for(k in styles){
 486             if(context[k] !== styles[k]) context[k] = styles[k];
 487         }
 488     }
 489 
 490     static getContext(canvas, className, id){
 491         if(CanvasImageRender.isCanvas(canvas) === false) canvas = document.createElement("canvas");
 492         const context = canvas.getContext("2d", CanvasImageRender.paramCon);
 493 
 494         if(typeof className === "string") canvas.className = className;
 495         if(typeof id === "string") canvas.setAttribute('id', id);
 496         
 497         return context;
 498     }
 499 
 500     static downloadImage(func){
 501         const input = document.createElement("input");
 502         input.type = "file";
 503         input.multiple = "multiple";
 504         input.accept = ".png, .jpg, .jpeg, .bmp, .gif";
 505     
 506         input.onchange = e => {
 507             if(e.target.files.length === 0) return;
 508             const fr = new FileReader();
 509             fr.onloadend = e1 => {
 510                 const img = new Image();
 511                 img.onload = () => func(img);
 512                 img.src = e1.target.result;
 513             }
 514 
 515             fr.readAsDataURL(e.target.files[0]);
 516         }
 517         
 518         input.click();
 519     }
 520 
 521     static isCanvasImage(img){ //OffscreenCanvas:ImageBitmap; CSSImageValue:
 522 
 523         return ImageBitmap["prototype"]["isPrototypeOf"](img) || 
 524         HTMLImageElement["prototype"]["isPrototypeOf"](img) || 
 525         HTMLCanvasElement["prototype"]["isPrototypeOf"](img) || 
 526         CanvasRenderingContext2D["prototype"]["isPrototypeOf"](img) || 
 527         HTMLVideoElement["prototype"]["isPrototypeOf"](img);
 528         
 529     }
 530 
 531     static isCanvas(canvas){
 532         
 533         return HTMLCanvasElement["prototype"]["isPrototypeOf"](canvas);
 534 
 535     }
 536 
 537     constructor(option = {}){
 538         this.list = [];
 539         this.box = new Box();
 540         this.domElementRect = new Box();
 541         this.context = CanvasImageRender.getContext(option.canvas, option.className, option.id);
 542         this.domElement = this.context.canvas;
 543 
 544         this.size(option.width, option.height);
 545     }
 546 
 547     isDraw(ca){
 548         return ca["visible"] === true && ca["image"] !== null && this["box"]["intersectsBox"](ca["box"]);
 549     }
 550 
 551     updateCanvasRect(){
 552         const rect = this.domElement.getBoundingClientRect();
 553         this.domElementRect.pos(rect.x, rect.y);
 554     }
 555 
 556     pos(x = 0, y = 0){
 557         this.domElement.style.left = x + "px";
 558         this.domElement.style.top = y + "px";
 559         if(this.domElement.parentElement !== null) this.updateCanvasRect();
 560         return this;
 561     }
 562 
 563     size(w = 1, h = 1){
 564         this.domElement.width = w;
 565         this.domElement.height = h;
 566         CanvasImageRender.setDefaultStyles(this.context);
 567         this.box.size(w, h);
 568         this.domElementRect.size(w, h);
 569         return this;
 570     }
 571 
 572     add(ca){
 573         if(CanvasImage.prototype.isPrototypeOf(ca) && !this.list.includes(ca)){
 574             const len = this.list.length;
 575             
 576             if(this.list[ca.index] === undefined){
 577                 ca.index = len;
 578                 this.list.push(ca);
 579 
 580             }
 581 
 582             else{
 583                 const arr = this.list.splice(ca.index);
 584                 this.list.push(ca);
 585                 for(let k = 0, c = arr.length; k < c; k++){
 586                     this.list.push(arr[k]);
 587                     arr[k].index++;
 588                 }
 589 
 590             }
 591 
 592         }
 593         
 594         return ca;
 595     }
 596 
 597     remove(ca){
 598         var i = ca.index;
 599 
 600         if(this.list[i] !== ca) i = this.list.indexOf(ca);
 601 
 602         if(i !== -1){
 603             this.list.splice(i, 1);
 604             for(let k = i, len = this.list.length; k < len; k++) this.list[k].index -= 1;
 605         }
 606 
 607         return ca;
 608     }
 609 
 610     render(parentElem = document.body){
 611         this.redraw();
 612         parentElem.appendChild(this.domElement);
 613         this.updateCanvasRect();
 614         return this;
 615     }
 616 
 617     initList(i){
 618         if(i === undefined || i < 0) i = 0;
 619         for(let k = i, len = this.list.length; k < len; k++) this.list[k].index = k;
 620         return this;
 621     }
 622 
 623     clear(){
 624 
 625         this['context']['clearRect'](0, 0, this['box']['w'], this['box']['h']);
 626 
 627     }
 628 
 629     draw(){
 630         const len = this["list"]["length"];
 631         for(let k = 0, ca; k < len; k++){
 632             ca = this["list"][k];
 633             if(this["isDraw"](ca) === true) this["_draw"](ca);
 634         }
 635     }
 636 
 637     computeOverlap(tar){
 638         //1 如果检索到与box相交的ca时, 合并其box执行以下步骤: 
 639         //2 检索 已检索过的 并且 没有相交的ca
 640         //3 如果存在相交的ca就合并box并继续第 2 步; 如果不存在继续第 1 步
 641         if(tar["overlap"] === null) tar["overlap"] = {box: new Box(), draw: false};
 642         const _list = CanvasImageRender.emptyArrA, list = CanvasImageRender.emptyArrB, len = this.list.length, box = tar["overlap"]["box"]["copy"](tar["box"]);
 643 
 644         for(let k = 0, i = 0, c = 0, _c = 0, a = _list, b = list, loop = false; k < len; k++){
 645             tar = this["list"][k];
 646             if(tar["overlap"] === null) tar["overlap"] = {box: new Box(), draw: false};
 647             
 648             if(this.isDraw(tar) === false){
 649                 if(tar["overlap"]["draw"] !== false) tar["overlap"]["draw"] = false;
 650                 continue;
 651             }
 652 
 653             if(box["intersectsBox"](tar["box"]) === true){
 654                 if(tar["overlap"]["draw"] !== true) tar["overlap"]["draw"] = true;
 655                 box["expand"](tar["box"]);
 656                 loop = true;
 657 
 658                 while(loop === true){
 659                     b["length"] = 0;
 660                     loop = false;
 661                     c = _c;
 662 
 663                     for(i = 0; i < c; i++){
 664                         tar = a[i];
 665 
 666                         if(box["intersectsBox"](tar["box"]) === true){
 667                             if(tar["overlap"]["draw"] !== true) tar["overlap"]["draw"] = true;
 668                             box["expand"](tar["box"]);
 669                             loop = true; _c--;
 670                         }
 671 
 672                         else b.push(tar);
 673                         
 674                     }
 675 
 676                     a = a === _list ? list : _list;
 677                     b = b === _list ? list : _list;
 678 
 679                 }
 680                 
 681             }
 682 
 683             else{
 684                 _c++;
 685                 a["push"](tar);
 686                 if(tar["overlap"]["draw"] !== false) tar["overlap"]["draw"] = false;
 687             }
 688 
 689         }
 690         
 691         _list.length = list.length = 0;
 692 
 693     }
 694 
 695     clearTarget(ca){
 696         this["computeOverlap"](ca);
 697         ca["overlap"]["draw"] = false;
 698         this["_drawTarget"](ca);
 699     }
 700 
 701     drawTarget(ca){
 702         this["computeOverlap"](ca);
 703         this["_drawTarget"](ca);
 704     }
 705 
 706     redraw(){
 707         this.clear();
 708         this.draw();
 709     }
 710 
 711     //限内部使用
 712     _drawImage(ca, ox = 0, oy = 0){
 713         if(ca["opacity"] === 1){
 714             if(ca.isScale === false) this["context"]["drawImage"](ca["image"], ca["box"]["x"]+ox, ca["box"]["y"]+oy);
 715             else if(ca.scale === null) this["context"]["drawImage"](ca["image"], ca["box"]["x"]+ox, ca["box"]["y"]+oy, ca.box.w, ca.box.h);
 716             //else this["context"]["drawImage"](ca["image"], ca.scale.x+ca.box.x, ca.scale.y+ca.box.y, ca.scale.w, ca.scale.h);
 717         }
 718 
 719         else{
 720             this["context"]["globalAlpha"] = ca["opacity"];
 721             if(ca.isScale === false) this["context"]["drawImage"](ca["image"], ca["box"]["x"]+ox, ca["box"]["y"]+oy);
 722             else if(ca.scale === null) this["context"]["drawImage"](ca["image"], ca["box"]["x"]+ox, ca["box"]["y"]+oy, ca.box.w, ca.box.h);
 723             //else this["context"]["drawImage"](ca["image"], ca.scale.x, ca.scale.y, ca.scale.w, ca.scale.h);
 724             this["context"]["globalAlpha"] = 1;
 725         }
 726     }
 727 
 728     _draw(ca){
 729         if(ca.rotate === null) this._drawImage(ca);
 730         else{
 731             const ox = ca.rotate.origin.x + ca.box.x, oy = ca.rotate.origin.y + ca.box.y;
 732             this.context.translate(ox, oy);
 733             this.context.rotate(ca.rotate.angle);
 734             this._drawImage(ca, -ox, -oy);
 735             this.context.rotate(-ca.rotate.angle);
 736             this.context.translate(-ox, -oy);
 737         }
 738     }
 739     
 740     _drawTarget(ca){
 741         const len = this["list"]["length"];
 742         this["context"]["clearRect"](ca["overlap"]["box"]["x"], ca["overlap"]["box"]["y"], ca["overlap"]["box"]["w"], ca["overlap"]["box"]["h"]);
 743 
 744         for(let k = 0; k < len; k++){
 745             ca = this["list"][k];
 746             if(ca["overlap"]["draw"] === true) this._draw(ca);
 747             
 748         }
 749 
 750     }
 751 
 752 }
 753 
 754 
 755 
 756 
 757 /* CanvasImage 
 758 parameter: 
 759     image
 760 
 761 attribute:
 762     opacity: Float;     //透明度; 值0至1之间; 默认1;
 763     visible: Boolean;    //默认true; 完全隐藏(既不绘制视图, 也不触发绑定的事件)
 764     box: Box;             //.x.y 图像的位置, .w.h 图像的宽高;
 765     rotate: Roate;        //旋转; 默认 null (注意: 旋转的原点是相对于 box.x.y 的)
 766     此属性暂未实现//scale: Box;            //缩放; .x.y中心点, .w.h缩放; 默认 null; (注意: 缩放的原点是相对于 box.x.y 的)
 767     x, y: Number;        //this.box 的 .x.y
 768 
 769     //以下属性不建议直接修改
 770     overlap: Object; //CanvasImageRender.computeOverlaps(ca) 方法更新
 771     index: Integer; //CanvasImageRender.index(ca) 修改
 772 
 773     //只读
 774     width, height, image; isRotate, isScale
 775 
 776 method:
 777     setImage(image): this;         //设置图像 (image 如果是 CanvasImage 并它正在加载图像, 则会在它加载完成时自动设置 image);
 778     load(src, onload): this;    //加载并设置图像 (onload 如果是 CanvasImageRender 则加载完后自动调用一次 redraw 或 render 方法);
 779     pos(x, y): this;             //设置位置; x 可以是: Number, Object{x,y}
 780     scale(w, h): this;            //设置缩放; w 可以是: Number, Box, CanvasImage 
 781 
 782 demo:
 783     //CanvasImageRender
 784     const cir = new CanvasImageRender({width: WORLD.width, height: WORLD.height});
 785     cir.domElement.style = `
 786         position: absolute;
 787         z-index: 9999;
 788         background: rgb(127,127,127);
 789     `;
 790 
 791     //values
 792     const ciA = cir.add(new CanvasImage()).pos(59, 59).load("view/examples/img/test.png", cir);
 793     ciA.opacity = 0.2; //设置透明度
 794 
 795     const ciB = cir.add(new CanvasImage(ciA)).pos(59, 120);
 796     ciB.rotate = new Rotate().toAngle(45); //旋转45度
 797 
 798     //event
 799     const cie = new CanvasImageEvent(cir); //注意: CanvasImage 的缩放和旋转都会影响到 CanvasImageEvent
 800     cie.add(ciB, "click", event => { //ciB 添加 点击事件
 801         console.log("click ciB: ", event);
 802     });
 803 */
 804 class CanvasImage{
 805 
 806     #image = null;
 807     #isLoadImage = false;
 808 
 809     get image(){return this.#image;}
 810     get isScale(){return this.#image.width !== this.box.w || this.#image.height !== this.box.h;}
 811     get width(){return this.#image.width;}
 812     get height(){return this.#image.height;}
 813     get isLoadImage(){return this.#isLoadImage;}
 814 
 815     get x(){return this.box.x;}
 816     get y(){return this.box.y;}
 817     set x(v){this.box.x = v;}
 818     set y(v){this.box.y = v;}
 819     
 820     constructor(image){
 821         this.opacity = 1;
 822         this.visible = true;
 823         this.box = new Box();
 824         this.rotate = null;
 825         //this.scale = null;
 826 
 827         //以下属性不建议直接修改
 828         this.overlap = null;
 829         this.index = -1;
 830         //this.loadImage_cis = undefined;
 831 
 832         this.setImage(image);
 833     }
 834 
 835     pos(x, y){
 836         if(UTILS.isNumber(x)){
 837             this.box.x = x;
 838             this.box.y = y;
 839         }
 840         else if(UTILS.isObject(x)){
 841             this.box.x = x.x;
 842             this.box.y = x.y;
 843         }
 844         return this;
 845     }
 846 
 847     load(src, onload){
 848         this.#isLoadImage = true;
 849         const image = new Image();
 850         
 851         image.onload = () => {
 852             this.setImage(image);
 853             this.#isLoadImage = false;
 854 
 855             if(Array.isArray(this.loadImage_cis)){
 856                 this.loadImage_cis.forEach(ci => ci.setImage(image));
 857                 delete this.loadImage_cis;
 858             }
 859 
 860             if(typeof onload === "function") onload(image);
 861             else if(CanvasImageRender.prototype.isPrototypeOf(onload)){
 862                 if(onload.domElement.parentElement !== null) onload.redraw();
 863                 else onload.render();
 864             }
 865         }
 866 
 867         image.src = src;
 868         return this;
 869     }
 870 
 871     setImage(image){
 872         if(CanvasImageRender.isCanvasImage(image)){
 873             this.box.size(image.width, image.height);
 874             this.#image = image;
 875         }
 876         else if(CanvasImage.prototype.isPrototypeOf(image)){
 877             if(image.isLoadImage){
 878                 if(Array.isArray(image.loadImage_cis)) image.loadImage_cis.push(this);
 879                 else image.loadImage_cis = [this];
 880             }
 881             else this.setImage(image.image);
 882         }
 883         else{
 884             this.box.size(0, 0);
 885             this.#image = null;
 886         }
 887         return this;
 888     }
 889 
 890 }
 891 
 892 
 893 
 894 /* CanvasAnimateExtend
 895 
 896 demo:
 897     //CanvasImageRender
 898     const cir = new CanvasImageRender({width: WORLD.width, height: WORLD.height});
 899     cir.domElement.style = `
 900         position: absolute;
 901         z-index: 9999;
 902         background: rgb(127,127,127);
 903     `;
 904 
 905     //values
 906     cir.add(new CanvasImageExtend())
 907     .pos(59, 180)
 908     .load("view/examples/img/test.png", cir);
 909 
 910     cir.add(new CanvasImageExtend())
 911     .pos(59, 180)
 912     .text("value", "red")
 913     .rect().stroke()
 914 
 915 */
 916 class CanvasImageExtend extends CanvasImage{
 917 
 918     constructor(canvas){
 919         super(canvas);
 920     }
 921 
 922     setImage(image){
 923         if(CanvasImageRender.isCanvas(image)){ //image 如果是画布
 924             super.setImage(image);
 925             this.context = CanvasImageRender.getContext(image);
 926             this.size(image.width, image.height);
 927         }
 928         else{
 929             if(CanvasImageRender.isCanvasImage(image)){ //image 如果是图像
 930                 this.context = CanvasImageRender.getContext();
 931                 super.setImage(this.context.canvas);
 932                 this.size(image.width, image.height);
 933                 this.context.drawImage(image, 0, 0);
 934             }else{ //image 如果是其它对象
 935                 if(image) super.setImage(image);
 936                 else{
 937                     this.context = CanvasImageRender.getContext();
 938                     super.setImage(this.context.canvas);
 939                     this.size(this.width, this.height);
 940                 }
 941             }
 942         }
 943         
 944         return this;
 945     }
 946 
 947     clear(){
 948         this.context.clearRect(0, 0, this.width, this.height);
 949         return this;
 950     }
 951 
 952     size(w, h){
 953         this.box.size(w, h);
 954         this.image.width = w;
 955         this.image.height = h;
 956         CanvasImageRender.setDefaultStyles(this.context);
 957         this.fontSize = parseFloat(CanvasImageRender.defaultStyles.font);
 958         this.fillStyle = CanvasImageRender.defaultStyles.fillStyle;
 959         this.strokeStyle = CanvasImageRender.defaultStyles.strokeStyle;
 960         this.shadowColor = CanvasImageRender.defaultStyles.shadowColor
 961         return this;
 962     }
 963 
 964     shadow(shadowColor = "rgba(0,0,0,0)", shadowBlur, shadowOffsetX, shadowOffsetY){
 965         const con = this.context;
 966         if(typeof shadowColor === "string" && this.shadowColor !== shadowColor) this.shadowColor = con.shadowColor = shadowColor;
 967         if(con.shadowBlur !== shadowBlur && typeof shadowBlur === "number") con.shadowBlur = shadowBlur;
 968         if(con.shadowOffsetX !== shadowOffsetX && typeof shadowOffsetX === "number") con.shadowOffsetX = shadowOffsetX;
 969         if(con.shadowOffsetY !== shadowOffsetY && typeof shadowOffsetY === "number") con.shadowOffsetY = shadowOffsetY;
 970         return this;
 971     }
 972 
 973     line(x, y, x1, y1){
 974         this.context.beginPath();
 975         this.context.moveTo(x, y);
 976         this.context.lineTo(x1, y1);
 977         return this;
 978     }
 979 
 980     path(arr, close = false){
 981         const con = this.context;
 982         con.beginPath();
 983         con.moveTo(arr[0], arr[1]);
 984         for(let k = 2, len = arr.length; k < len; k+=2) con.lineTo(arr[k], arr[k+1]);
 985         if(close === true) con.closePath();
 986         return this;
 987     }
 988 
 989     rect(r){
 990         const con = this.context, s = con.lineWidth;
 991         var x = s / 2,
 992             y = s / 2,
 993             w = this.box.w - s,
 994             h = this.box.h - s;
 995 
 996         if(UTILS.isNumber(r) === false || r <= 0) r = Math.min(this.box.w, this.box.h) * 0.05;
 997 
 998         const _x = x + r, 
 999         _y = y + r, 
1000         mx = x + w, 
1001         my = y + h, 
1002         _mx = mx - r, 
1003         _my = my - r;
1004         
1005         //
1006         con.moveTo(_x, y);
1007         con.lineTo(_mx, y);
1008         con.arcTo(mx, y, mx, _y, r);
1009 
1010         //
1011         con.lineTo(mx, _y);
1012         con.lineTo(mx, _my);
1013         con.arcTo(mx, my, _x, my, r);
1014 
1015         //
1016         con.lineTo(_x, my);
1017         con.lineTo(_mx, my);
1018         con.arcTo(x, my, x, _my, r);
1019 
1020         //
1021         con.lineTo(x, _y);
1022         con.lineTo(x, _my);
1023         con.arcTo(x, y, _x, y, r);
1024 
1025         return this;
1026     }
1027 
1028     stroke(color, lineWidth){
1029         if(typeof color === "string" && this.strokeStyle !== color) this.strokeStyle = this.context.strokeStyle = color;
1030         if(UTILS.isNumber(lineWidth) && this.context.lineWidth !== lineWidth) this.context.lineWidth = lineWidth;
1031         this.context.stroke();
1032         return this;
1033     }
1034 
1035     fill(color){
1036         if(typeof color === "string" && this.fillStyle !== color) this.fillStyle = this.context.fillStyle = color;
1037         this.context.fill();
1038         return this;
1039     }
1040 
1041     text(value, color, fontSize = 12, x = -1, y = -1){
1042         if(this.fontSize !== fontSize){
1043             this.fontSize = fontSize;
1044             this.context.font = fontSize+"px SimSun, Songti SC";
1045         }
1046 
1047         const textWidth = this.context.measureText(value).width;
1048         if(textWidth < this.box.w || this.fontSize < this.box.h){
1049             this.size(textWidth+4, this.fontSize+4);
1050             this.fontSize = fontSize;
1051             this.context.font = fontSize+"px SimSun, Songti SC";
1052         }
1053 
1054         if(x === -1) x = (this.box.w - textWidth) / 2;
1055         if(y === -1) y = (this.box.h - this.fontSize) / 2;
1056 
1057         if(typeof color === "string" && this.fillStyle !== color) this.fillStyle = this.context.fillStyle = color;
1058         this.context.fillText(value, x, y);
1059 
1060         return this;
1061     }
1062 
1063 }
1064 
1065 
1066 
1067 export {
1068     CanvasImageEvent, 
1069     CanvasImageRender,
1070     CanvasImage, 
1071     CanvasImageExtend,
1072 }

 

posted @ 2022-09-05 19:53  鸡儿er  阅读(211)  评论(0编辑  收藏  举报